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.tree.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.DataTreeCandidate;
26 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
27 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
28 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
29 import org.opendaylight.controller.md.sal.dom.store.impl.tree.data.NodeModification;
30 import org.opendaylight.controller.md.sal.dom.store.impl.tree.data.StoreMetadataNode;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
35 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.common.base.Optional;
42 import com.google.common.base.Preconditions;
43 import com.google.common.collect.HashMultimap;
44 import com.google.common.collect.ImmutableList;
45 import com.google.common.collect.Iterables;
46 import com.google.common.collect.Multimap;
50 * Resolve Data Change Events based on modifications and listeners
52 * Computes data change events for all affected registered listeners in data
55 * Prerequisites for computation is to set all parameters properly:
57 * <li>{@link #setRootPath(InstanceIdentifier)} - Root path of datastore
58 * <li>{@link #setListenerRoot(ListenerTree)} - Root of listener registration
59 * tree, which contains listeners to be notified
60 * <li>{@link #setModificationRoot(NodeModification)} - Modification root, for
61 * which events should be computed
62 * <li>{@link #setBeforeRoot(Optional)} - State of before modification occurred
63 * <li>{@link #setAfterRoot(Optional)} - State of after modification occurred
67 final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
68 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
69 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
71 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
72 private final DataTreeCandidate candidate;
73 private final ListenerTree listenerRoot;
75 public ResolveDataChangeEventsTask(DataTreeCandidate candidate, ListenerTree listenerTree) {
76 this.candidate = Preconditions.checkNotNull(candidate);
77 this.listenerRoot = Preconditions.checkNotNull(listenerTree);
81 * Resolves and creates Notification Tasks
83 * Implementation of done as Map-Reduce with two steps: 1. resolving events
84 * and their mapping to listeners 2. merging events affecting same listener
86 * @return Iterable of Notification Tasks which needs to be executed in
87 * order to delivery data change events.
90 public Iterable<ChangeListenerNotifyTask> call() {
91 try (final Walker w = listenerRoot.getWalker()) {
92 resolveAnyChangeEvent(candidate.getRootPath(), Collections.singleton(w.getRootNode()),
93 candidate.getModificationRoot(), Optional.fromNullable(candidate.getBeforeRoot()),
94 Optional.fromNullable(candidate.getAfterRoot()));
95 return createNotificationTasks();
101 * Walks map of listeners to data change events, creates notification
104 * Walks map of registered and affected listeners and creates notification
105 * tasks from set of listeners and events to be delivered.
107 * If set of listeners has more then one event (applicable to wildcarded
108 * listeners), merges all data change events into one, final which contains
109 * all separate updates.
111 * Dispatch between merge variant and reuse variant of notification task is
113 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
115 * @return Collection of notification tasks.
117 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
118 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
119 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
120 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
122 return taskListBuilder.build();
126 * Adds notification task to task list.
128 * If entry collection contains one event, this event is reused and added to
129 * notification tasks for listeners (see
130 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
131 * . Otherwise events are merged by scope and distributed between listeners
132 * to particular scope. See
133 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
136 * @param taskListBuilder
140 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
141 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
143 if (!entries.isEmpty()) {
144 if (entries.size() == 1) {
145 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
147 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
154 * Add notification deliveries task to the listener.
157 * @param taskListBuilder
161 private static void addNotificationTaskByScope(
162 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
163 final DOMImmutableDataChangeEvent event) {
164 DataChangeScope eventScope = event.getScope();
165 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
166 DataChangeScope listenerScope = listenerReg.getScope();
167 List<DataChangeListenerRegistration<?>> listenerSet = Collections
168 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
169 if (eventScope == DataChangeScope.BASE) {
170 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
171 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
172 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
173 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
174 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
181 * Add notification tasks with merged event
183 * Separate Events by scope and creates merged notification tasks for each
184 * and every scope which is present.
186 * Adds merged events to task list based on scope requested by client.
188 * @param taskListBuilder
192 private static void addNotificationTasksAndMergeEvents(
193 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
194 final Collection<DOMImmutableDataChangeEvent> entries) {
196 final Builder baseBuilder = builder(DataChangeScope.BASE);
197 final Builder oneBuilder = builder(DataChangeScope.ONE);
198 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
200 boolean baseModified = false;
201 boolean oneModified = false;
202 boolean subtreeModified = false;
203 for (final DOMImmutableDataChangeEvent entry : entries) {
204 switch (entry.getScope()) {
205 // Absence of breaks is intentional here. Subtree contains base and
206 // one, one also contains base
208 baseBuilder.merge(entry);
211 oneBuilder.merge(entry);
214 subtreeBuilder.merge(entry);
215 subtreeModified = true;
220 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
223 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
225 if (subtreeModified) {
226 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
230 private static void addNotificationTaskExclusively(
231 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
232 final DOMImmutableDataChangeEvent event) {
233 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
234 if (listener.getScope() == event.getScope()) {
235 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
236 .<DataChangeListenerRegistration<?>> singleton(listener);
237 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
243 * Resolves data change event for supplied node
246 * Path to current node in tree
248 * Collection of Listener registration nodes interested in
250 * @param modification
251 * Modification of current node
253 * - Original (before) state of current node
255 * - After state of current node
256 * @return Data Change Event of this node and all it's children
258 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
259 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
260 final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
261 // No listeners are present in listener registration subtree
262 // no before and after state is present
263 if (!before.isPresent() && !after.isPresent()) {
266 switch (modification.getModificationType()) {
267 case SUBTREE_MODIFIED:
268 return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
271 if (before.isPresent()) {
272 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
274 return resolveCreateEvent(path, listeners, after.get());
277 return resolveDeleteEvent(path, listeners, before.get());
284 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
285 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
286 final NormalizedNode<?, ?> afterData) {
288 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
289 // Node is container (contains child) and we have interested
290 // listeners registered for it, that means we need to do
291 // resolution of changes on children level and can not
292 // shortcut resolution.
293 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
294 @SuppressWarnings("unchecked")
295 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
296 @SuppressWarnings("unchecked")
297 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
298 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
299 } else if (!beforeData.equals(afterData)) {
300 // Node is either of Leaf type (does not contain child nodes)
301 // or we do not have listeners, so normal equals method is
302 // sufficient for determining change.
303 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
304 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
305 .addUpdated(path, beforeData, afterData).build();
306 addPartialTask(listeners, event);
313 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
314 final Collection<Node> listeners,
315 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
316 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
317 final Set<PathArgument> alreadyProcessed = new HashSet<>();
318 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
320 DataChangeScope potentialScope = DataChangeScope.BASE;
321 // We look at all children from before and compare it with after state.
322 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
323 PathArgument childId = beforeChild.getIdentifier();
324 alreadyProcessed.add(childId);
325 InstanceIdentifier childPath = append(path, childId);
326 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
327 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
328 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
329 beforeChild, afterChild);
330 // If change is empty (equals to NO_CHANGE)
331 if (childChange != NO_CHANGE) {
332 childChanges.add(childChange);
337 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
338 PathArgument childId = afterChild.getIdentifier();
339 if (!alreadyProcessed.contains(childId)) {
340 // We did not processed that child already
341 // and it was not present in previous loop, that means it is
343 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
344 InstanceIdentifier childPath = append(path,childId);
345 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
346 DOMImmutableDataChangeEvent.getCreateEventFactory()));
349 if (childChanges.isEmpty()) {
353 Builder eventBuilder = builder(potentialScope) //
354 .setBefore(beforeCont) //
356 .addUpdated(path, beforeCont, afterCont);
357 for (DOMImmutableDataChangeEvent childChange : childChanges) {
358 eventBuilder.merge(childChange);
361 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
362 addPartialTask(listeners, replaceEvent);
366 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
367 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
368 final Optional<NormalizedNode<PathArgument, ?>> after) {
370 if (after.isPresent()) {
371 // REPLACE or SUBTREE Modified
372 return resolveReplacedEvent(path, listeners, before, after.get());
375 // AFTER state is not present - child was deleted.
376 return resolveSameEventRecursivelly(path, listeners, before,
377 DOMImmutableDataChangeEvent.getRemoveEventFactory());
382 * Resolves create events deep down the interest listener tree.
390 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
391 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
392 @SuppressWarnings({ "unchecked", "rawtypes" })
393 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
394 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
397 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
398 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
400 @SuppressWarnings({ "unchecked", "rawtypes" })
401 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
402 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
405 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
406 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
407 final SimpleEventFactory eventFactory) {
408 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
409 DOMImmutableDataChangeEvent propagateEvent = event;
410 // We have listeners for this node or it's children, so we will try
411 // to do additional processing
412 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
413 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
415 Builder eventBuilder = builder(DataChangeScope.BASE);
416 eventBuilder.merge(event);
417 eventBuilder.setBefore(event.getOriginalSubtree());
418 eventBuilder.setAfter(event.getUpdatedSubtree());
420 // Node has children, so we will try to resolve it's children
422 @SuppressWarnings("unchecked")
423 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
424 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
425 PathArgument childId = child.getIdentifier();
426 LOG.trace("Resolving event for child {}", childId);
427 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
428 eventBuilder.merge(resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory));
430 propagateEvent = eventBuilder.build();
432 // We do not dispatch leaf events since Binding Aware components do not support them.
433 propagateEvent = builder(DataChangeScope.BASE).build();
435 if (!listeners.isEmpty()) {
436 addPartialTask(listeners, propagateEvent);
438 return propagateEvent;
441 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
442 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
443 final StoreMetadataNode before, final StoreMetadataNode after) {
445 Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
447 Builder subtree = builder(DataChangeScope.SUBTREE).setBefore(before.getData()).setAfter(after.getData());
449 for (NodeModification childMod : modification.getModifications()) {
450 PathArgument childId = childMod.getIdentifier();
451 InstanceIdentifier childPath = append(path, childId);
452 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
454 Optional<StoreMetadataNode> childBefore = before.getChild(childId);
455 Optional<StoreMetadataNode> childAfter = after.getChild(childId);
457 switch (childMod.getModificationType()) {
461 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
463 case SUBTREE_MODIFIED:
464 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
472 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
473 subtree.merge(oneChangeEvent);
474 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
475 if (!listeners.isEmpty()) {
476 addPartialTask(listeners, oneChangeEvent);
477 addPartialTask(listeners, subtreeEvent);
482 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
483 final DOMImmutableDataChangeEvent event) {
484 for (ListenerTree.Node listenerNode : listeners) {
485 if (!listenerNode.getListeners().isEmpty()) {
486 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
487 events.put(listenerNode, event);
493 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
494 final PathArgument child) {
495 if (parentNodes.isEmpty()) {
496 return Collections.emptyList();
498 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
499 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
500 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
501 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
503 addChildrenNodesToBuilder(result, parentNodes, child);
504 return result.build();
507 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
508 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
509 for (ListenerTree.Node node : parentNodes) {
510 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
511 if (child.isPresent()) {
512 result.add(child.get());
517 public static ResolveDataChangeEventsTask create(DataTreeCandidate candidate, ListenerTree listenerTree) {
518 return new ResolveDataChangeEventsTask(candidate, listenerTree);