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;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.HashMultimap;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.Iterables;
17 import com.google.common.collect.Multimap;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map.Entry;
25 import java.util.concurrent.Callable;
27 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
28 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
29 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
30 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
31 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
32 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
33 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
34 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
35 import org.opendaylight.yangtools.util.concurrent.NotificationManager;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
41 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
43 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
44 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Resolve Data Change Events based on modifications and listeners
52 * Computes data change events for all affected registered listeners in data
55 final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
56 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
57 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
59 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
60 private final DataTreeCandidate candidate;
61 private final ListenerTree listenerRoot;
63 @SuppressWarnings("rawtypes")
64 private final NotificationManager<AsyncDataChangeListener, AsyncDataChangeEvent> notificationMgr;
66 @SuppressWarnings("rawtypes")
67 public ResolveDataChangeEventsTask(final DataTreeCandidate candidate, final ListenerTree listenerTree,
68 final NotificationManager<AsyncDataChangeListener, AsyncDataChangeEvent> notificationMgr) {
69 this.candidate = Preconditions.checkNotNull(candidate);
70 this.listenerRoot = Preconditions.checkNotNull(listenerTree);
71 this.notificationMgr = Preconditions.checkNotNull(notificationMgr);
75 * Resolves and creates Notification Tasks
77 * Implementation of done as Map-Reduce with two steps: 1. resolving events
78 * and their mapping to listeners 2. merging events affecting same listener
80 * @return An {@link Iterable} of Notification Tasks which needs to be executed in
81 * order to delivery data change events.
84 public Iterable<ChangeListenerNotifyTask> call() {
85 try (final Walker w = listenerRoot.getWalker()) {
86 resolveAnyChangeEvent(candidate.getRootPath(), Collections.singleton(w.getRootNode()), candidate.getRootNode());
87 return createNotificationTasks();
93 * Walks map of listeners to data change events, creates notification
96 * Walks map of registered and affected listeners and creates notification
97 * tasks from set of listeners and events to be delivered.
99 * If set of listeners has more then one event (applicable to wildcarded
100 * listeners), merges all data change events into one, final which contains
101 * all separate updates.
103 * Dispatch between merge variant and reuse variant of notification task is
105 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
107 * @return Collection of notification tasks.
109 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
110 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
111 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
112 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
114 return taskListBuilder.build();
118 * Adds notification task to task list.
120 * If entry collection contains one event, this event is reused and added to
121 * notification tasks for listeners (see
122 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
123 * . Otherwise events are merged by scope and distributed between listeners
124 * to particular scope. See
125 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
128 * @param taskListBuilder
132 private void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
133 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
135 if (!entries.isEmpty()) {
136 if (entries.size() == 1) {
137 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
139 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
146 * Add notification deliveries task to the listener.
149 * @param taskListBuilder
153 private void addNotificationTaskByScope(
154 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
155 final DOMImmutableDataChangeEvent event) {
156 DataChangeScope eventScope = event.getScope();
157 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
158 DataChangeScope listenerScope = listenerReg.getScope();
159 List<DataChangeListenerRegistration<?>> listenerSet = Collections
160 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
161 if (eventScope == DataChangeScope.BASE) {
162 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event, notificationMgr));
163 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
164 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event, notificationMgr));
165 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
166 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event, notificationMgr));
173 * Add notification tasks with merged event
175 * Separate Events by scope and creates merged notification tasks for each
176 * and every scope which is present.
178 * Adds merged events to task list based on scope requested by client.
180 * @param taskListBuilder
184 private void addNotificationTasksAndMergeEvents(
185 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
186 final Collection<DOMImmutableDataChangeEvent> entries) {
188 final Builder baseBuilder = builder(DataChangeScope.BASE);
189 final Builder oneBuilder = builder(DataChangeScope.ONE);
190 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
192 boolean baseModified = false;
193 boolean oneModified = false;
194 boolean subtreeModified = false;
195 for (final DOMImmutableDataChangeEvent entry : entries) {
196 switch (entry.getScope()) {
197 // Absence of breaks is intentional here. Subtree contains base and
198 // one, one also contains base
200 baseBuilder.merge(entry);
203 oneBuilder.merge(entry);
206 subtreeBuilder.merge(entry);
207 subtreeModified = true;
212 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
215 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
217 if (subtreeModified) {
218 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
222 private void addNotificationTaskExclusively(
223 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
224 final DOMImmutableDataChangeEvent event) {
225 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
226 if (listener.getScope() == event.getScope()) {
227 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
228 .<DataChangeListenerRegistration<?>> singleton(listener);
229 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event, notificationMgr));
235 * Resolves data change event for supplied node
238 * Path to current node in tree
240 * Collection of Listener registration nodes interested in
242 * @param modification
243 * Modification of current node
245 * - Original (before) state of current node
247 * - After state of current node
248 * @return Data Change Event of this node and all it's children
250 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final YangInstanceIdentifier path,
251 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode node) {
253 if (node.getModificationType() != ModificationType.UNMODIFIED &&
254 !node.getDataAfter().isPresent() && !node.getDataBefore().isPresent()) {
255 LOG.debug("Modification at {} has type {}, but no before- and after-data. Assuming unchanged.",
256 path, node.getModificationType());
260 // no before and after state is present
262 switch (node.getModificationType()) {
263 case SUBTREE_MODIFIED:
264 return resolveSubtreeChangeEvent(path, listeners, node);
267 Preconditions.checkArgument(node.getDataAfter().isPresent(),
268 "Modification at {} has type {} but no after-data", path, node.getModificationType());
269 if (node.getDataBefore().isPresent()) {
270 return resolveReplacedEvent(path, listeners, node.getDataBefore().get(), node.getDataAfter().get());
272 return resolveCreateEvent(path, listeners, node.getDataAfter().get());
275 Preconditions.checkArgument(node.getDataBefore().isPresent(),
276 "Modification at {} has type {} but no before-data", path, node.getModificationType());
277 return resolveDeleteEvent(path, listeners, node.getDataBefore().get());
282 throw new IllegalStateException(String.format("Unhandled node state %s at %s", node.getModificationType(), path));
285 private DOMImmutableDataChangeEvent resolveReplacedEvent(final YangInstanceIdentifier path,
286 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
287 final NormalizedNode<?, ?> afterData) {
289 // FIXME: BUG-1493: check the listeners to prune unneeded changes:
290 // for subtrees, we have to do all
291 // for one, we need to expand children
292 // for base, we just report replacement
294 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
295 // Node is container (contains child) and we have interested
296 // listeners registered for it, that means we need to do
297 // resolution of changes on children level and can not
298 // shortcut resolution.
299 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
300 @SuppressWarnings("unchecked")
301 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
302 @SuppressWarnings("unchecked")
303 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
304 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
305 } else if (!beforeData.equals(afterData)) {
306 // Node is Leaf type (does not contain child nodes)
307 // so normal equals method is sufficient for determining change.
308 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
309 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
310 .addUpdated(path, beforeData, afterData).build();
311 addPartialTask(listeners, event);
318 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final YangInstanceIdentifier path,
319 final Collection<Node> listeners,
320 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
321 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
322 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
324 // We look at all children from before and compare it with after state.
325 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
326 final PathArgument childId = beforeChild.getIdentifier();
328 YangInstanceIdentifier childPath = path.node(childId);
329 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
330 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
331 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
332 beforeChild, afterChild);
333 // If change is empty (equals to NO_CHANGE)
334 if (childChange != NO_CHANGE) {
335 childChanges.add(childChange);
339 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
340 final PathArgument childId = afterChild.getIdentifier();
343 * We have already iterated of the before-children, so have already
344 * emitted modify/delete events. This means the child has been
347 if (!beforeCont.getChild(childId).isPresent()) {
348 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
349 YangInstanceIdentifier childPath = path.node(childId);
350 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
351 DOMImmutableDataChangeEvent.getCreateEventFactory()));
354 if (childChanges.isEmpty()) {
358 Builder eventBuilder = builder(DataChangeScope.BASE) //
359 .setBefore(beforeCont) //
361 .addUpdated(path, beforeCont, afterCont);
362 for (DOMImmutableDataChangeEvent childChange : childChanges) {
363 eventBuilder.merge(childChange);
366 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
367 addPartialTask(listeners, replaceEvent);
371 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final YangInstanceIdentifier path,
372 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
373 final Optional<NormalizedNode<PathArgument, ?>> after) {
375 if (after.isPresent()) {
376 // REPLACE or SUBTREE Modified
377 return resolveReplacedEvent(path, listeners, before, after.get());
380 // AFTER state is not present - child was deleted.
381 return resolveSameEventRecursivelly(path, listeners, before,
382 DOMImmutableDataChangeEvent.getRemoveEventFactory());
387 * Resolves create events deep down the interest listener tree.
395 private DOMImmutableDataChangeEvent resolveCreateEvent(final YangInstanceIdentifier path,
396 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
397 @SuppressWarnings({ "unchecked", "rawtypes" })
398 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
399 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
402 private DOMImmutableDataChangeEvent resolveDeleteEvent(final YangInstanceIdentifier path,
403 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
405 @SuppressWarnings({ "unchecked", "rawtypes" })
406 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
407 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
410 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final YangInstanceIdentifier path,
411 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
412 final SimpleEventFactory eventFactory) {
413 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
414 DOMImmutableDataChangeEvent propagateEvent = event;
415 // We have listeners for this node or it's children, so we will try
416 // to do additional processing
417 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
418 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
420 Builder eventBuilder = builder(DataChangeScope.BASE);
421 eventBuilder.merge(event);
422 eventBuilder.setBefore(event.getOriginalSubtree());
423 eventBuilder.setAfter(event.getUpdatedSubtree());
425 // Node has children, so we will try to resolve it's children
427 @SuppressWarnings("unchecked")
428 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
429 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
430 PathArgument childId = child.getIdentifier();
431 LOG.trace("Resolving event for child {}", childId);
432 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
433 eventBuilder.merge(resolveSameEventRecursivelly(path.node(childId), childListeners, child, eventFactory));
435 propagateEvent = eventBuilder.build();
437 if (!listeners.isEmpty()) {
438 addPartialTask(listeners, propagateEvent);
440 return propagateEvent;
443 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final YangInstanceIdentifier path,
444 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
446 Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
447 Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
449 Builder one = builder(DataChangeScope.ONE).
450 setBefore(modification.getDataBefore().get()).
451 setAfter(modification.getDataAfter().get());
452 Builder subtree = builder(DataChangeScope.SUBTREE).
453 setBefore(modification.getDataBefore().get()).
454 setAfter(modification.getDataAfter().get());
455 boolean oneModified = false;
456 for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
457 PathArgument childId = childMod.getIdentifier();
458 YangInstanceIdentifier childPath = path.node(childId);
459 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
462 switch (childMod.getModificationType()) {
466 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
469 case SUBTREE_MODIFIED:
470 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
477 final DOMImmutableDataChangeEvent oneChangeEvent;
479 one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
480 oneChangeEvent = one.build();
481 subtree.merge(oneChangeEvent);
483 oneChangeEvent = null;
484 subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
486 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
487 if (!listeners.isEmpty()) {
488 if(oneChangeEvent != null) {
489 addPartialTask(listeners, oneChangeEvent);
491 addPartialTask(listeners, subtreeEvent);
496 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
497 final DOMImmutableDataChangeEvent event) {
498 for (ListenerTree.Node listenerNode : listeners) {
499 if (!listenerNode.getListeners().isEmpty()) {
500 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
501 events.put(listenerNode, event);
507 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
508 final PathArgument child) {
509 if (parentNodes.isEmpty()) {
510 return Collections.emptyList();
512 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
513 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
514 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
515 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
517 addChildrenNodesToBuilder(result, parentNodes, child);
518 return result.build();
521 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
522 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
523 for (ListenerTree.Node node : parentNodes) {
524 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
525 if (child.isPresent()) {
526 result.add(child.get());
531 @SuppressWarnings("rawtypes")
532 public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate,
533 final ListenerTree listenerTree,
534 final NotificationManager<AsyncDataChangeListener,AsyncDataChangeEvent> notificationMgr) {
535 return new ResolveDataChangeEventsTask(candidate, listenerTree, notificationMgr);