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.DataTreeCandidateNode;
27 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
28 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
29 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
30 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ModificationType;
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()), candidate.getRootNode());
93 return createNotificationTasks();
99 * Walks map of listeners to data change events, creates notification
102 * Walks map of registered and affected listeners and creates notification
103 * tasks from set of listeners and events to be delivered.
105 * If set of listeners has more then one event (applicable to wildcarded
106 * listeners), merges all data change events into one, final which contains
107 * all separate updates.
109 * Dispatch between merge variant and reuse variant of notification task is
111 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
113 * @return Collection of notification tasks.
115 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
116 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
117 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
118 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
120 return taskListBuilder.build();
124 * Adds notification task to task list.
126 * If entry collection contains one event, this event is reused and added to
127 * notification tasks for listeners (see
128 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
129 * . Otherwise events are merged by scope and distributed between listeners
130 * to particular scope. See
131 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
134 * @param taskListBuilder
138 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
139 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
141 if (!entries.isEmpty()) {
142 if (entries.size() == 1) {
143 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
145 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
152 * Add notification deliveries task to the listener.
155 * @param taskListBuilder
159 private static void addNotificationTaskByScope(
160 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
161 final DOMImmutableDataChangeEvent event) {
162 DataChangeScope eventScope = event.getScope();
163 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
164 DataChangeScope listenerScope = listenerReg.getScope();
165 List<DataChangeListenerRegistration<?>> listenerSet = Collections
166 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
167 if (eventScope == DataChangeScope.BASE) {
168 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
169 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
170 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
171 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
172 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
179 * Add notification tasks with merged event
181 * Separate Events by scope and creates merged notification tasks for each
182 * and every scope which is present.
184 * Adds merged events to task list based on scope requested by client.
186 * @param taskListBuilder
190 private static void addNotificationTasksAndMergeEvents(
191 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
192 final Collection<DOMImmutableDataChangeEvent> entries) {
194 final Builder baseBuilder = builder(DataChangeScope.BASE);
195 final Builder oneBuilder = builder(DataChangeScope.ONE);
196 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
198 boolean baseModified = false;
199 boolean oneModified = false;
200 boolean subtreeModified = false;
201 for (final DOMImmutableDataChangeEvent entry : entries) {
202 switch (entry.getScope()) {
203 // Absence of breaks is intentional here. Subtree contains base and
204 // one, one also contains base
206 baseBuilder.merge(entry);
209 oneBuilder.merge(entry);
212 subtreeBuilder.merge(entry);
213 subtreeModified = true;
218 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
221 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
223 if (subtreeModified) {
224 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
228 private static void addNotificationTaskExclusively(
229 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
230 final DOMImmutableDataChangeEvent event) {
231 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
232 if (listener.getScope() == event.getScope()) {
233 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
234 .<DataChangeListenerRegistration<?>> singleton(listener);
235 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
241 * Resolves data change event for supplied node
244 * Path to current node in tree
246 * Collection of Listener registration nodes interested in
248 * @param modification
249 * Modification of current node
251 * - Original (before) state of current node
253 * - After state of current node
254 * @return Data Change Event of this node and all it's children
256 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
257 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode node) {
259 if (node.getModificationType() != ModificationType.UNMODIFIED &&
260 !node.getDataAfter().isPresent() && !node.getDataBefore().isPresent()) {
261 LOG.debug("Modification at {} has type {}, but no before- and after-data. Assuming unchanged.",
262 path, node.getModificationType());
266 // no before and after state is present
268 switch (node.getModificationType()) {
269 case SUBTREE_MODIFIED:
270 return resolveSubtreeChangeEvent(path, listeners, node);
273 Preconditions.checkArgument(node.getDataAfter().isPresent(),
274 "Modification at {} has type {} but no after-data", path, node.getModificationType());
275 if (node.getDataBefore().isPresent()) {
276 return resolveReplacedEvent(path, listeners, node.getDataBefore().get(), node.getDataAfter().get());
278 return resolveCreateEvent(path, listeners, node.getDataAfter().get());
281 Preconditions.checkArgument(node.getDataBefore().isPresent(),
282 "Modification at {} has type {} but no before-data", path, node.getModificationType());
283 return resolveDeleteEvent(path, listeners, node.getDataBefore().get());
288 throw new IllegalStateException(String.format("Unhandled node state %s at %s", node.getModificationType(), path));
291 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
292 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
293 final NormalizedNode<?, ?> afterData) {
295 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
296 // Node is container (contains child) and we have interested
297 // listeners registered for it, that means we need to do
298 // resolution of changes on children level and can not
299 // shortcut resolution.
300 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
301 @SuppressWarnings("unchecked")
302 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
303 @SuppressWarnings("unchecked")
304 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
305 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
306 } else if (!beforeData.equals(afterData)) {
307 // Node is either of Leaf type (does not contain child nodes)
308 // or we do not have listeners, so normal equals method is
309 // sufficient for determining change.
310 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
311 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
312 .addUpdated(path, beforeData, afterData).build();
313 addPartialTask(listeners, event);
320 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
321 final Collection<Node> listeners,
322 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
323 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
324 final Set<PathArgument> alreadyProcessed = new HashSet<>();
325 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
327 DataChangeScope potentialScope = DataChangeScope.BASE;
328 // We look at all children from before and compare it with after state.
329 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
330 PathArgument childId = beforeChild.getIdentifier();
331 alreadyProcessed.add(childId);
332 InstanceIdentifier childPath = append(path, childId);
333 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
334 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
335 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
336 beforeChild, afterChild);
337 // If change is empty (equals to NO_CHANGE)
338 if (childChange != NO_CHANGE) {
339 childChanges.add(childChange);
344 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
345 PathArgument childId = afterChild.getIdentifier();
346 if (!alreadyProcessed.contains(childId)) {
347 // We did not processed that child already
348 // and it was not present in previous loop, that means it is
350 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
351 InstanceIdentifier childPath = append(path,childId);
352 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
353 DOMImmutableDataChangeEvent.getCreateEventFactory()));
356 if (childChanges.isEmpty()) {
360 Builder eventBuilder = builder(potentialScope) //
361 .setBefore(beforeCont) //
363 .addUpdated(path, beforeCont, afterCont);
364 for (DOMImmutableDataChangeEvent childChange : childChanges) {
365 eventBuilder.merge(childChange);
368 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
369 addPartialTask(listeners, replaceEvent);
373 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
374 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
375 final Optional<NormalizedNode<PathArgument, ?>> after) {
377 if (after.isPresent()) {
378 // REPLACE or SUBTREE Modified
379 return resolveReplacedEvent(path, listeners, before, after.get());
382 // AFTER state is not present - child was deleted.
383 return resolveSameEventRecursivelly(path, listeners, before,
384 DOMImmutableDataChangeEvent.getRemoveEventFactory());
389 * Resolves create events deep down the interest listener tree.
397 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
398 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
399 @SuppressWarnings({ "unchecked", "rawtypes" })
400 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
401 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
404 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
405 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
407 @SuppressWarnings({ "unchecked", "rawtypes" })
408 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
409 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
412 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
413 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
414 final SimpleEventFactory eventFactory) {
415 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
416 DOMImmutableDataChangeEvent propagateEvent = event;
417 // We have listeners for this node or it's children, so we will try
418 // to do additional processing
419 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
420 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
422 Builder eventBuilder = builder(DataChangeScope.BASE);
423 eventBuilder.merge(event);
424 eventBuilder.setBefore(event.getOriginalSubtree());
425 eventBuilder.setAfter(event.getUpdatedSubtree());
427 // Node has children, so we will try to resolve it's children
429 @SuppressWarnings("unchecked")
430 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
431 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
432 PathArgument childId = child.getIdentifier();
433 LOG.trace("Resolving event for child {}", childId);
434 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
435 eventBuilder.merge(resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory));
437 propagateEvent = eventBuilder.build();
439 // We do not dispatch leaf events since Binding Aware components do not support them.
440 propagateEvent = builder(DataChangeScope.BASE).build();
442 if (!listeners.isEmpty()) {
443 addPartialTask(listeners, propagateEvent);
445 return propagateEvent;
448 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
449 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
451 Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
452 Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
454 Builder one = builder(DataChangeScope.ONE).
455 setBefore(modification.getDataBefore().get()).
456 setAfter(modification.getDataAfter().get());
457 Builder subtree = builder(DataChangeScope.SUBTREE).
458 setBefore(modification.getDataBefore().get()).
459 setAfter(modification.getDataAfter().get());
461 for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
462 PathArgument childId = childMod.getIdentifier();
463 InstanceIdentifier childPath = append(path, childId);
464 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
466 switch (childMod.getModificationType()) {
470 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
472 case SUBTREE_MODIFIED:
473 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
480 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
481 subtree.merge(oneChangeEvent);
482 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
483 if (!listeners.isEmpty()) {
484 addPartialTask(listeners, oneChangeEvent);
485 addPartialTask(listeners, subtreeEvent);
490 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
491 final DOMImmutableDataChangeEvent event) {
492 for (ListenerTree.Node listenerNode : listeners) {
493 if (!listenerNode.getListeners().isEmpty()) {
494 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
495 events.put(listenerNode, event);
501 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
502 final PathArgument child) {
503 if (parentNodes.isEmpty()) {
504 return Collections.emptyList();
506 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
507 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
508 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
509 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
511 addChildrenNodesToBuilder(result, parentNodes, child);
512 return result.build();
515 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
516 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
517 for (ListenerTree.Node node : parentNodes) {
518 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
519 if (child.isPresent()) {
520 result.add(child.get());
525 public static ResolveDataChangeEventsTask create(DataTreeCandidate candidate, ListenerTree listenerTree) {
526 return new ResolveDataChangeEventsTask(candidate, listenerTree);