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());
312 if (before.isPresent()) {
313 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
315 return resolveCreateEvent(path, listeners, after.get());
318 return resolveDeleteEvent(path, listeners, before.get());
325 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
326 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
327 final NormalizedNode<?, ?> afterData) {
329 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?> && !listeners.isEmpty()) {
330 // Node is container (contains child) and we have interested
331 // listeners registered for it, that means we need to do
332 // resolution of changes on children level and can not
333 // shortcut resolution.
335 @SuppressWarnings("unchecked")
336 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
337 @SuppressWarnings("unchecked")
338 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
339 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
340 } else if (!beforeData.equals(afterData)) {
341 // Node is either of Leaf type (does not contain child nodes)
342 // or we do not have listeners, so normal equals method is
343 // sufficient for determining change.
345 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
346 .addUpdated(path, beforeData, afterData).build();
347 addPartialTask(listeners, event);
354 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
355 final Collection<Node> listeners,
356 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
357 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
358 final Set<PathArgument> alreadyProcessed = new HashSet<>();
359 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
361 DataChangeScope potentialScope = DataChangeScope.BASE;
362 // We look at all children from before and compare it with after state.
363 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
364 PathArgument childId = beforeChild.getIdentifier();
365 alreadyProcessed.add(childId);
366 InstanceIdentifier childPath = append(path, childId);
367 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
368 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
369 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
370 beforeChild, afterChild);
371 // If change is empty (equals to NO_CHANGE)
372 if (childChange != NO_CHANGE) {
373 childChanges.add(childChange);
378 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
379 PathArgument childId = afterChild.getIdentifier();
380 if (!alreadyProcessed.contains(childId)) {
381 // We did not processed that child already
382 // and it was not present in previous loop, that means it is
384 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
385 InstanceIdentifier childPath = append(path,childId);
386 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
387 DOMImmutableDataChangeEvent.getCreateEventFactory()));
390 if (childChanges.isEmpty()) {
394 Builder eventBuilder = builder(potentialScope) //
395 .setBefore(beforeCont) //
396 .setAfter(afterCont);
397 for (DOMImmutableDataChangeEvent childChange : childChanges) {
398 eventBuilder.merge(childChange);
401 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
402 addPartialTask(listeners, replaceEvent);
406 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
407 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
408 final Optional<NormalizedNode<PathArgument, ?>> after) {
410 if (after.isPresent()) {
411 // REPLACE or SUBTREE Modified
412 return resolveReplacedEvent(path, listeners, before, after.get());
415 // AFTER state is not present - child was deleted.
416 return resolveSameEventRecursivelly(path, listeners, before,
417 DOMImmutableDataChangeEvent.getRemoveEventFactory());
422 * Resolves create events deep down the interest listener tree.
430 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
431 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
432 @SuppressWarnings({ "unchecked", "rawtypes" })
433 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
434 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
437 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
438 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
440 @SuppressWarnings({ "unchecked", "rawtypes" })
441 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
442 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
445 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
446 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
447 final SimpleEventFactory eventFactory) {
449 DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
451 if (!listeners.isEmpty()) {
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 // Node has children, so we will try to resolve it's children
457 @SuppressWarnings("unchecked")
458 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
459 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
460 PathArgument childId = child.getIdentifier();
461 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
462 if (!childListeners.isEmpty()) {
463 resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory);
467 addPartialTask(listeners, event);
472 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
473 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
474 final StoreMetadataNode before, final StoreMetadataNode after) {
476 Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
478 Builder subtree = builder(DataChangeScope.SUBTREE);
480 for (NodeModification childMod : modification.getModifications()) {
481 PathArgument childId = childMod.getIdentifier();
482 InstanceIdentifier childPath = append(path, childId);
483 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
485 Optional<StoreMetadataNode> childBefore = before.getChild(childId);
486 Optional<StoreMetadataNode> childAfter = after.getChild(childId);
488 switch (childMod.getModificationType()) {
491 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
493 case SUBTREE_MODIFIED:
494 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
502 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
503 subtree.merge(oneChangeEvent);
504 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
505 if (!listeners.isEmpty()) {
506 addPartialTask(listeners, oneChangeEvent);
507 addPartialTask(listeners, subtreeEvent);
512 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
513 final DOMImmutableDataChangeEvent event) {
515 for (ListenerTree.Node listenerNode : listeners) {
516 if (!listenerNode.getListeners().isEmpty()) {
517 events.put(listenerNode, event);
523 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
524 final PathArgument child) {
525 if (parentNodes.isEmpty()) {
526 return Collections.emptyList();
528 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
529 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
530 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
531 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
533 addChildrenNodesToBuilder(result, parentNodes, child);
534 return result.build();
537 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
538 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
539 for (ListenerTree.Node node : parentNodes) {
540 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
541 if (child.isPresent()) {
542 result.add(child.get());
547 public static ResolveDataChangeEventsTask create() {
548 return new ResolveDataChangeEventsTask();