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 static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
11 import static org.opendaylight.controller.md.sal.dom.store.impl.StoreUtils.append;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.LinkedList;
17 import java.util.List;
18 import java.util.Map.Entry;
20 import java.util.concurrent.Callable;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
24 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
25 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
26 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
27 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
28 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
29 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.common.base.Optional;
41 import com.google.common.collect.HashMultimap;
42 import com.google.common.collect.ImmutableList;
43 import com.google.common.collect.Iterables;
44 import com.google.common.collect.Multimap;
48 * Resolve Data Change Events based on modifications and listeners
50 * Computes data change events for all affected registered listeners in data
53 * Prerequisites for computation is to set all parameters properly:
55 * <li>{@link #setRootPath(InstanceIdentifier)} - Root path of datastore
56 * <li>{@link #setListenerRoot(ListenerTree)} - Root of listener registration
57 * tree, which contains listeners to be notified
58 * <li>{@link #setModificationRoot(NodeModification)} - Modification root, for
59 * which events should be computed
60 * <li>{@link #setBeforeRoot(Optional)} - State of before modification occurred
61 * <li>{@link #setAfterRoot(Optional)} - State of after modification occurred
65 public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
66 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
67 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
69 private InstanceIdentifier rootPath;
70 private ListenerTree listenerRoot;
71 private NodeModification modificationRoot;
72 private Optional<StoreMetadataNode> beforeRoot;
73 private Optional<StoreMetadataNode> afterRoot;
74 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
76 protected InstanceIdentifier getRootPath() {
80 protected ResolveDataChangeEventsTask setRootPath(final InstanceIdentifier rootPath) {
81 this.rootPath = rootPath;
85 protected ListenerTree getListenerRoot() {
89 protected ResolveDataChangeEventsTask setListenerRoot(final ListenerTree listenerRoot) {
90 this.listenerRoot = listenerRoot;
94 protected NodeModification getModificationRoot() {
95 return modificationRoot;
98 protected ResolveDataChangeEventsTask setModificationRoot(final NodeModification modificationRoot) {
99 this.modificationRoot = modificationRoot;
103 protected Optional<StoreMetadataNode> getBeforeRoot() {
107 protected ResolveDataChangeEventsTask setBeforeRoot(final Optional<StoreMetadataNode> beforeRoot) {
108 this.beforeRoot = beforeRoot;
112 protected Optional<StoreMetadataNode> getAfterRoot() {
116 protected ResolveDataChangeEventsTask setAfterRoot(final Optional<StoreMetadataNode> afterRoot) {
117 this.afterRoot = afterRoot;
122 * Resolves and creates Notification Tasks
124 * Implementation of done as Map-Reduce with two steps: 1. resolving events
125 * and their mapping to listeners 2. merging events affecting same listener
127 * @return Iterable of Notification Tasks which needs to be executed in
128 * order to delivery data change events.
131 public Iterable<ChangeListenerNotifyTask> call() {
132 LOG.trace("Resolving events for {}", modificationRoot);
134 try (final Walker w = listenerRoot.getWalker()) {
135 resolveAnyChangeEvent(rootPath, Collections.singleton(w.getRootNode()), modificationRoot, beforeRoot,
137 return createNotificationTasks();
143 * Walks map of listeners to data change events, creates notification
146 * Walks map of registered and affected listeners and creates notification
147 * tasks from set of listeners and events to be delivered.
149 * If set of listeners has more then one event (applicable to wildcarded
150 * listeners), merges all data change events into one, final which contains
151 * all separate updates.
153 * Dispatch between merge variant and reuse variant of notification task is
155 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
157 * @return Collection of notification tasks.
159 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
160 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
161 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
162 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
164 return taskListBuilder.build();
168 * Adds notification task to task list.
170 * If entry collection contains one event, this event is reused and added to
171 * notification tasks for listeners (see
172 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
173 * . Otherwise events are merged by scope and distributed between listeners
174 * to particular scope. See
175 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
178 * @param taskListBuilder
182 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
183 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
185 if (!entries.isEmpty()) {
186 if (entries.size() == 1) {
187 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
189 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
196 * Add notification deliveries task to the listener.
199 * @param taskListBuilder
203 private static void addNotificationTaskByScope(
204 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
205 final DOMImmutableDataChangeEvent event) {
206 DataChangeScope eventScope = event.getScope();
207 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
208 DataChangeScope listenerScope = listenerReg.getScope();
209 List<DataChangeListenerRegistration<?>> listenerSet = Collections
210 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
211 if (eventScope == DataChangeScope.BASE) {
212 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
213 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
214 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
215 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
216 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
223 * Add notification tasks with merged event
225 * Separate Events by scope and creates merged notification tasks for each
226 * and every scope which is present.
228 * Adds merged events to task list based on scope requested by client.
230 * @param taskListBuilder
234 private static void addNotificationTasksAndMergeEvents(
235 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
236 final Collection<DOMImmutableDataChangeEvent> entries) {
238 final Builder baseBuilder = builder(DataChangeScope.BASE);
239 final Builder oneBuilder = builder(DataChangeScope.ONE);
240 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
242 boolean baseModified = false;
243 boolean oneModified = false;
244 boolean subtreeModified = false;
245 for (final DOMImmutableDataChangeEvent entry : entries) {
246 switch (entry.getScope()) {
247 // Absence of breaks is intentional here. Subtree contains base and
248 // one, one also contains base
250 baseBuilder.merge(entry);
253 oneBuilder.merge(entry);
256 subtreeBuilder.merge(entry);
257 subtreeModified = true;
262 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
265 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
267 if (subtreeModified) {
268 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
272 private static void addNotificationTaskExclusively(
273 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
274 final DOMImmutableDataChangeEvent event) {
275 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
276 if (listener.getScope() == event.getScope()) {
277 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
278 .<DataChangeListenerRegistration<?>> singleton(listener);
279 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
285 * Resolves data change event for supplied node
288 * Path to current node in tree
290 * Collection of Listener registration nodes interested in
292 * @param modification
293 * Modification of current node
295 * - Original (before) state of current node
297 * - After state of current node
298 * @return Data Change Event of this node and all it's children
300 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
301 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
302 final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
303 // No listeners are present in listener registration subtree
304 // no before and after state is present
305 if (!before.isPresent() && !after.isPresent()) {
308 switch (modification.getModificationType()) {
309 case SUBTREE_MODIFIED:
310 return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
313 if (before.isPresent()) {
314 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
316 return resolveCreateEvent(path, listeners, after.get());
319 return resolveDeleteEvent(path, listeners, before.get());
326 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
327 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
328 final NormalizedNode<?, ?> afterData) {
330 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
331 // Node is container (contains child) and we have interested
332 // listeners registered for it, that means we need to do
333 // resolution of changes on children level and can not
334 // shortcut resolution.
335 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
336 @SuppressWarnings("unchecked")
337 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
338 @SuppressWarnings("unchecked")
339 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
340 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
341 } else if (!beforeData.equals(afterData)) {
342 // Node is either of Leaf type (does not contain child nodes)
343 // or we do not have listeners, so normal equals method is
344 // sufficient for determining change.
345 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
346 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
347 .addUpdated(path, beforeData, afterData).build();
348 addPartialTask(listeners, event);
355 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
356 final Collection<Node> listeners,
357 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
358 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
359 final Set<PathArgument> alreadyProcessed = new HashSet<>();
360 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
362 DataChangeScope potentialScope = DataChangeScope.BASE;
363 // We look at all children from before and compare it with after state.
364 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
365 PathArgument childId = beforeChild.getIdentifier();
366 alreadyProcessed.add(childId);
367 InstanceIdentifier childPath = append(path, childId);
368 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
369 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
370 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
371 beforeChild, afterChild);
372 // If change is empty (equals to NO_CHANGE)
373 if (childChange != NO_CHANGE) {
374 childChanges.add(childChange);
379 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
380 PathArgument childId = afterChild.getIdentifier();
381 if (!alreadyProcessed.contains(childId)) {
382 // We did not processed that child already
383 // and it was not present in previous loop, that means it is
385 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
386 InstanceIdentifier childPath = append(path,childId);
387 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
388 DOMImmutableDataChangeEvent.getCreateEventFactory()));
391 if (childChanges.isEmpty()) {
395 Builder eventBuilder = builder(potentialScope) //
396 .setBefore(beforeCont) //
398 .addUpdated(path, beforeCont, afterCont);
399 for (DOMImmutableDataChangeEvent childChange : childChanges) {
400 eventBuilder.merge(childChange);
403 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
404 addPartialTask(listeners, replaceEvent);
408 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
409 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
410 final Optional<NormalizedNode<PathArgument, ?>> after) {
412 if (after.isPresent()) {
413 // REPLACE or SUBTREE Modified
414 return resolveReplacedEvent(path, listeners, before, after.get());
417 // AFTER state is not present - child was deleted.
418 return resolveSameEventRecursivelly(path, listeners, before,
419 DOMImmutableDataChangeEvent.getRemoveEventFactory());
424 * Resolves create events deep down the interest listener tree.
432 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
433 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
434 @SuppressWarnings({ "unchecked", "rawtypes" })
435 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
436 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
439 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
440 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
442 @SuppressWarnings({ "unchecked", "rawtypes" })
443 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
444 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
447 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
448 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
449 final SimpleEventFactory eventFactory) {
450 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
451 DOMImmutableDataChangeEvent propagateEvent = event;
452 // We have listeners for this node or it's children, so we will try
453 // to do additional processing
454 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
455 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
457 Builder eventBuilder = builder(DataChangeScope.BASE);
458 eventBuilder.merge(event);
459 eventBuilder.setBefore(event.getOriginalSubtree());
460 eventBuilder.setAfter(event.getUpdatedSubtree());
462 // Node has children, so we will try to resolve it's children
464 @SuppressWarnings("unchecked")
465 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
466 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
467 PathArgument childId = child.getIdentifier();
468 LOG.trace("Resolving event for child {}", childId);
469 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
470 eventBuilder.merge(resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory));
472 propagateEvent = eventBuilder.build();
474 // We do not dispatch leaf events since Binding Aware components do not support them.
475 propagateEvent = builder(DataChangeScope.BASE).build();
477 if (!listeners.isEmpty()) {
478 addPartialTask(listeners, propagateEvent);
480 return propagateEvent;
483 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
484 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
485 final StoreMetadataNode before, final StoreMetadataNode after) {
487 Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
489 Builder subtree = builder(DataChangeScope.SUBTREE).setBefore(before.getData()).setAfter(after.getData());
491 for (NodeModification childMod : modification.getModifications()) {
492 PathArgument childId = childMod.getIdentifier();
493 InstanceIdentifier childPath = append(path, childId);
494 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
496 Optional<StoreMetadataNode> childBefore = before.getChild(childId);
497 Optional<StoreMetadataNode> childAfter = after.getChild(childId);
499 switch (childMod.getModificationType()) {
503 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
505 case SUBTREE_MODIFIED:
506 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
514 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
515 subtree.merge(oneChangeEvent);
516 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
517 if (!listeners.isEmpty()) {
518 addPartialTask(listeners, oneChangeEvent);
519 addPartialTask(listeners, subtreeEvent);
524 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
525 final DOMImmutableDataChangeEvent event) {
526 for (ListenerTree.Node listenerNode : listeners) {
527 if (!listenerNode.getListeners().isEmpty()) {
528 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
529 events.put(listenerNode, event);
535 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
536 final PathArgument child) {
537 if (parentNodes.isEmpty()) {
538 return Collections.emptyList();
540 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
541 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
542 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
543 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
545 addChildrenNodesToBuilder(result, parentNodes, child);
546 return result.build();
549 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
550 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
551 for (ListenerTree.Node node : parentNodes) {
552 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
553 if (child.isPresent()) {
554 result.add(child.get());
559 public static ResolveDataChangeEventsTask create() {
560 return new ResolveDataChangeEventsTask();