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 java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Map.Entry;
19 import java.util.concurrent.Callable;
21 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
22 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
23 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
24 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
25 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
26 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
35 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.common.base.Optional;
41 import com.google.common.base.Preconditions;
42 import com.google.common.collect.HashMultimap;
43 import com.google.common.collect.ImmutableList;
44 import com.google.common.collect.Iterables;
45 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 final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
54 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
55 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
57 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
58 private final DataTreeCandidate candidate;
59 private final ListenerTree listenerRoot;
61 public ResolveDataChangeEventsTask(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
62 this.candidate = Preconditions.checkNotNull(candidate);
63 this.listenerRoot = Preconditions.checkNotNull(listenerTree);
67 * Resolves and creates Notification Tasks
69 * Implementation of done as Map-Reduce with two steps: 1. resolving events
70 * and their mapping to listeners 2. merging events affecting same listener
72 * @return An {@link Iterable} of Notification Tasks which needs to be executed in
73 * order to delivery data change events.
76 public Iterable<ChangeListenerNotifyTask> call() {
77 try (final Walker w = listenerRoot.getWalker()) {
78 resolveAnyChangeEvent(candidate.getRootPath(), Collections.singleton(w.getRootNode()), candidate.getRootNode());
79 return createNotificationTasks();
85 * Walks map of listeners to data change events, creates notification
88 * Walks map of registered and affected listeners and creates notification
89 * tasks from set of listeners and events to be delivered.
91 * If set of listeners has more then one event (applicable to wildcarded
92 * listeners), merges all data change events into one, final which contains
93 * all separate updates.
95 * Dispatch between merge variant and reuse variant of notification task is
97 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
99 * @return Collection of notification tasks.
101 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
102 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
103 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
104 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
106 return taskListBuilder.build();
110 * Adds notification task to task list.
112 * If entry collection contains one event, this event is reused and added to
113 * notification tasks for listeners (see
114 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
115 * . Otherwise events are merged by scope and distributed between listeners
116 * to particular scope. See
117 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
120 * @param taskListBuilder
124 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
125 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
127 if (!entries.isEmpty()) {
128 if (entries.size() == 1) {
129 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
131 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
138 * Add notification deliveries task to the listener.
141 * @param taskListBuilder
145 private static void addNotificationTaskByScope(
146 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
147 final DOMImmutableDataChangeEvent event) {
148 DataChangeScope eventScope = event.getScope();
149 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
150 DataChangeScope listenerScope = listenerReg.getScope();
151 List<DataChangeListenerRegistration<?>> listenerSet = Collections
152 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
153 if (eventScope == DataChangeScope.BASE) {
154 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
155 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
156 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
157 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
158 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
165 * Add notification tasks with merged event
167 * Separate Events by scope and creates merged notification tasks for each
168 * and every scope which is present.
170 * Adds merged events to task list based on scope requested by client.
172 * @param taskListBuilder
176 private static void addNotificationTasksAndMergeEvents(
177 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
178 final Collection<DOMImmutableDataChangeEvent> entries) {
180 final Builder baseBuilder = builder(DataChangeScope.BASE);
181 final Builder oneBuilder = builder(DataChangeScope.ONE);
182 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
184 boolean baseModified = false;
185 boolean oneModified = false;
186 boolean subtreeModified = false;
187 for (final DOMImmutableDataChangeEvent entry : entries) {
188 switch (entry.getScope()) {
189 // Absence of breaks is intentional here. Subtree contains base and
190 // one, one also contains base
192 baseBuilder.merge(entry);
195 oneBuilder.merge(entry);
198 subtreeBuilder.merge(entry);
199 subtreeModified = true;
204 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
207 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
209 if (subtreeModified) {
210 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
214 private static void addNotificationTaskExclusively(
215 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
216 final DOMImmutableDataChangeEvent event) {
217 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
218 if (listener.getScope() == event.getScope()) {
219 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
220 .<DataChangeListenerRegistration<?>> singleton(listener);
221 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
227 * Resolves data change event for supplied node
230 * Path to current node in tree
232 * Collection of Listener registration nodes interested in
234 * @param modification
235 * Modification of current node
237 * - Original (before) state of current node
239 * - After state of current node
240 * @return Data Change Event of this node and all it's children
242 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
243 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode node) {
245 if (node.getModificationType() != ModificationType.UNMODIFIED &&
246 !node.getDataAfter().isPresent() && !node.getDataBefore().isPresent()) {
247 LOG.debug("Modification at {} has type {}, but no before- and after-data. Assuming unchanged.",
248 path, node.getModificationType());
252 // no before and after state is present
254 switch (node.getModificationType()) {
255 case SUBTREE_MODIFIED:
256 return resolveSubtreeChangeEvent(path, listeners, node);
259 Preconditions.checkArgument(node.getDataAfter().isPresent(),
260 "Modification at {} has type {} but no after-data", path, node.getModificationType());
261 if (node.getDataBefore().isPresent()) {
262 return resolveReplacedEvent(path, listeners, node.getDataBefore().get(), node.getDataAfter().get());
264 return resolveCreateEvent(path, listeners, node.getDataAfter().get());
267 Preconditions.checkArgument(node.getDataBefore().isPresent(),
268 "Modification at {} has type {} but no before-data", path, node.getModificationType());
269 return resolveDeleteEvent(path, listeners, node.getDataBefore().get());
274 throw new IllegalStateException(String.format("Unhandled node state %s at %s", node.getModificationType(), path));
277 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
278 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
279 final NormalizedNode<?, ?> afterData) {
281 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
282 // Node is container (contains child) and we have interested
283 // listeners registered for it, that means we need to do
284 // resolution of changes on children level and can not
285 // shortcut resolution.
286 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
287 @SuppressWarnings("unchecked")
288 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
289 @SuppressWarnings("unchecked")
290 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
291 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
292 } else if (!beforeData.equals(afterData)) {
293 // Node is Leaf type (does not contain child nodes)
294 // so normal equals method is sufficient for determining change.
295 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
296 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
297 .addUpdated(path, beforeData, afterData).build();
298 addPartialTask(listeners, event);
305 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
306 final Collection<Node> listeners,
307 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
308 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
309 final Set<PathArgument> alreadyProcessed = new HashSet<>();
310 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
312 DataChangeScope potentialScope = DataChangeScope.BASE;
313 // We look at all children from before and compare it with after state.
314 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
315 PathArgument childId = beforeChild.getIdentifier();
316 alreadyProcessed.add(childId);
317 InstanceIdentifier childPath = path.node(childId);
318 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
319 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
320 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
321 beforeChild, afterChild);
322 // If change is empty (equals to NO_CHANGE)
323 if (childChange != NO_CHANGE) {
324 childChanges.add(childChange);
329 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
330 PathArgument childId = afterChild.getIdentifier();
331 if (!alreadyProcessed.contains(childId)) {
332 // We did not processed that child already
333 // and it was not present in previous loop, that means it is
335 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
336 InstanceIdentifier childPath = path.node(childId);
337 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
338 DOMImmutableDataChangeEvent.getCreateEventFactory()));
341 if (childChanges.isEmpty()) {
345 Builder eventBuilder = builder(potentialScope) //
346 .setBefore(beforeCont) //
348 .addUpdated(path, beforeCont, afterCont);
349 for (DOMImmutableDataChangeEvent childChange : childChanges) {
350 eventBuilder.merge(childChange);
353 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
354 addPartialTask(listeners, replaceEvent);
358 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
359 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
360 final Optional<NormalizedNode<PathArgument, ?>> after) {
362 if (after.isPresent()) {
363 // REPLACE or SUBTREE Modified
364 return resolveReplacedEvent(path, listeners, before, after.get());
367 // AFTER state is not present - child was deleted.
368 return resolveSameEventRecursivelly(path, listeners, before,
369 DOMImmutableDataChangeEvent.getRemoveEventFactory());
374 * Resolves create events deep down the interest listener tree.
382 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
383 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
384 @SuppressWarnings({ "unchecked", "rawtypes" })
385 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
386 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
389 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
390 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
392 @SuppressWarnings({ "unchecked", "rawtypes" })
393 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
394 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
397 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
398 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
399 final SimpleEventFactory eventFactory) {
400 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
401 DOMImmutableDataChangeEvent propagateEvent = event;
402 // We have listeners for this node or it's children, so we will try
403 // to do additional processing
404 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
405 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
407 Builder eventBuilder = builder(DataChangeScope.BASE);
408 eventBuilder.merge(event);
409 eventBuilder.setBefore(event.getOriginalSubtree());
410 eventBuilder.setAfter(event.getUpdatedSubtree());
412 // Node has children, so we will try to resolve it's children
414 @SuppressWarnings("unchecked")
415 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
416 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
417 PathArgument childId = child.getIdentifier();
418 LOG.trace("Resolving event for child {}", childId);
419 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
420 eventBuilder.merge(resolveSameEventRecursivelly(path.node(childId), childListeners, child, eventFactory));
422 propagateEvent = eventBuilder.build();
424 if (!listeners.isEmpty()) {
425 addPartialTask(listeners, propagateEvent);
427 return propagateEvent;
430 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
431 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
433 Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
434 Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
436 Builder one = builder(DataChangeScope.ONE).
437 setBefore(modification.getDataBefore().get()).
438 setAfter(modification.getDataAfter().get());
439 Builder subtree = builder(DataChangeScope.SUBTREE).
440 setBefore(modification.getDataBefore().get()).
441 setAfter(modification.getDataAfter().get());
442 boolean oneModified = false;
443 for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
444 PathArgument childId = childMod.getIdentifier();
445 InstanceIdentifier childPath = path.node(childId);
446 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
449 switch (childMod.getModificationType()) {
453 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
456 case SUBTREE_MODIFIED:
457 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
464 final DOMImmutableDataChangeEvent oneChangeEvent;
466 one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
467 oneChangeEvent = one.build();
468 subtree.merge(oneChangeEvent);
470 oneChangeEvent = null;
471 subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
473 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
474 if (!listeners.isEmpty()) {
475 if(oneChangeEvent != null) {
476 addPartialTask(listeners, oneChangeEvent);
478 addPartialTask(listeners, subtreeEvent);
483 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
484 final DOMImmutableDataChangeEvent event) {
485 for (ListenerTree.Node listenerNode : listeners) {
486 if (!listenerNode.getListeners().isEmpty()) {
487 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
488 events.put(listenerNode, event);
494 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
495 final PathArgument child) {
496 if (parentNodes.isEmpty()) {
497 return Collections.emptyList();
499 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
500 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
501 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
502 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
504 addChildrenNodesToBuilder(result, parentNodes, child);
505 return result.build();
508 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
509 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
510 for (ListenerTree.Node node : parentNodes) {
511 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
512 if (child.isPresent()) {
513 result.add(child.get());
518 public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
519 return new ResolveDataChangeEventsTask(candidate, listenerTree);