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.AsyncDataBroker.DataChangeScope;
28 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
29 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
30 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
31 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
32 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * Resolve Data Change Events based on modifications and listeners
49 * Computes data change events for all affected registered listeners in data
52 final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
53 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
54 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
56 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
57 private final DataTreeCandidate candidate;
58 private final ListenerTree listenerRoot;
60 public ResolveDataChangeEventsTask(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
61 this.candidate = Preconditions.checkNotNull(candidate);
62 this.listenerRoot = Preconditions.checkNotNull(listenerTree);
66 * Resolves and creates Notification Tasks
68 * Implementation of done as Map-Reduce with two steps: 1. resolving events
69 * and their mapping to listeners 2. merging events affecting same listener
71 * @return An {@link Iterable} of Notification Tasks which needs to be executed in
72 * order to delivery data change events.
75 public Iterable<ChangeListenerNotifyTask> call() {
76 try (final Walker w = listenerRoot.getWalker()) {
77 resolveAnyChangeEvent(candidate.getRootPath(), Collections.singleton(w.getRootNode()), candidate.getRootNode());
78 return createNotificationTasks();
84 * Walks map of listeners to data change events, creates notification
87 * Walks map of registered and affected listeners and creates notification
88 * tasks from set of listeners and events to be delivered.
90 * If set of listeners has more then one event (applicable to wildcarded
91 * listeners), merges all data change events into one, final which contains
92 * all separate updates.
94 * Dispatch between merge variant and reuse variant of notification task is
96 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
98 * @return Collection of notification tasks.
100 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
101 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
102 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
103 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
105 return taskListBuilder.build();
109 * Adds notification task to task list.
111 * If entry collection contains one event, this event is reused and added to
112 * notification tasks for listeners (see
113 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
114 * . Otherwise events are merged by scope and distributed between listeners
115 * to particular scope. See
116 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, java.util.Collection)}
119 * @param taskListBuilder
123 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
124 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
126 if (!entries.isEmpty()) {
127 if (entries.size() == 1) {
128 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
130 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
137 * Add notification deliveries task to the listener.
140 * @param taskListBuilder
144 private static void addNotificationTaskByScope(
145 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
146 final DOMImmutableDataChangeEvent event) {
147 DataChangeScope eventScope = event.getScope();
148 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
149 DataChangeScope listenerScope = listenerReg.getScope();
150 List<DataChangeListenerRegistration<?>> listenerSet = Collections
151 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
152 if (eventScope == DataChangeScope.BASE) {
153 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
154 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
155 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
156 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
157 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
164 * Add notification tasks with merged event
166 * Separate Events by scope and creates merged notification tasks for each
167 * and every scope which is present.
169 * Adds merged events to task list based on scope requested by client.
171 * @param taskListBuilder
175 private static void addNotificationTasksAndMergeEvents(
176 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
177 final Collection<DOMImmutableDataChangeEvent> entries) {
179 final Builder baseBuilder = builder(DataChangeScope.BASE);
180 final Builder oneBuilder = builder(DataChangeScope.ONE);
181 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
183 boolean baseModified = false;
184 boolean oneModified = false;
185 boolean subtreeModified = false;
186 for (final DOMImmutableDataChangeEvent entry : entries) {
187 switch (entry.getScope()) {
188 // Absence of breaks is intentional here. Subtree contains base and
189 // one, one also contains base
191 baseBuilder.merge(entry);
194 oneBuilder.merge(entry);
197 subtreeBuilder.merge(entry);
198 subtreeModified = true;
203 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
206 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
208 if (subtreeModified) {
209 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
213 private static void addNotificationTaskExclusively(
214 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
215 final DOMImmutableDataChangeEvent event) {
216 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
217 if (listener.getScope() == event.getScope()) {
218 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
219 .<DataChangeListenerRegistration<?>> singleton(listener);
220 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
226 * Resolves data change event for supplied node
229 * Path to current node in tree
231 * Collection of Listener registration nodes interested in
233 * @param modification
234 * Modification of current node
236 * - Original (before) state of current node
238 * - After state of current node
239 * @return Data Change Event of this node and all it's children
241 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final YangInstanceIdentifier path,
242 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode node) {
244 if (node.getModificationType() != ModificationType.UNMODIFIED &&
245 !node.getDataAfter().isPresent() && !node.getDataBefore().isPresent()) {
246 LOG.debug("Modification at {} has type {}, but no before- and after-data. Assuming unchanged.",
247 path, node.getModificationType());
251 // no before and after state is present
253 switch (node.getModificationType()) {
254 case SUBTREE_MODIFIED:
255 return resolveSubtreeChangeEvent(path, listeners, node);
258 Preconditions.checkArgument(node.getDataAfter().isPresent(),
259 "Modification at {} has type {} but no after-data", path, node.getModificationType());
260 if (node.getDataBefore().isPresent()) {
261 return resolveReplacedEvent(path, listeners, node.getDataBefore().get(), node.getDataAfter().get());
263 return resolveCreateEvent(path, listeners, node.getDataAfter().get());
266 Preconditions.checkArgument(node.getDataBefore().isPresent(),
267 "Modification at {} has type {} but no before-data", path, node.getModificationType());
268 return resolveDeleteEvent(path, listeners, node.getDataBefore().get());
273 throw new IllegalStateException(String.format("Unhandled node state %s at %s", node.getModificationType(), path));
276 private DOMImmutableDataChangeEvent resolveReplacedEvent(final YangInstanceIdentifier path,
277 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
278 final NormalizedNode<?, ?> afterData) {
280 // FIXME: BUG-1493: check the listeners to prune unneeded changes:
281 // for subtrees, we have to do all
282 // for one, we need to expand children
283 // for base, we just report replacement
285 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
286 // Node is container (contains child) and we have interested
287 // listeners registered for it, that means we need to do
288 // resolution of changes on children level and can not
289 // shortcut resolution.
290 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
291 @SuppressWarnings("unchecked")
292 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
293 @SuppressWarnings("unchecked")
294 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
295 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
296 } else if (!beforeData.equals(afterData)) {
297 // Node is Leaf type (does not contain child nodes)
298 // so normal equals method is sufficient for determining change.
299 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
300 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
301 .addUpdated(path, beforeData, afterData).build();
302 addPartialTask(listeners, event);
309 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final YangInstanceIdentifier path,
310 final Collection<Node> listeners,
311 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
312 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
313 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
315 // We look at all children from before and compare it with after state.
316 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
317 final PathArgument childId = beforeChild.getIdentifier();
319 YangInstanceIdentifier childPath = path.node(childId);
320 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
321 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
322 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
323 beforeChild, afterChild);
324 // If change is empty (equals to NO_CHANGE)
325 if (childChange != NO_CHANGE) {
326 childChanges.add(childChange);
330 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
331 final PathArgument childId = afterChild.getIdentifier();
334 * We have already iterated of the before-children, so have already
335 * emitted modify/delete events. This means the child has been
338 if (!beforeCont.getChild(childId).isPresent()) {
339 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
340 YangInstanceIdentifier childPath = path.node(childId);
341 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
342 DOMImmutableDataChangeEvent.getCreateEventFactory()));
345 if (childChanges.isEmpty()) {
349 Builder eventBuilder = builder(DataChangeScope.BASE) //
350 .setBefore(beforeCont) //
352 .addUpdated(path, beforeCont, afterCont);
353 for (DOMImmutableDataChangeEvent childChange : childChanges) {
354 eventBuilder.merge(childChange);
357 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
358 addPartialTask(listeners, replaceEvent);
362 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final YangInstanceIdentifier path,
363 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
364 final Optional<NormalizedNode<PathArgument, ?>> after) {
366 if (after.isPresent()) {
367 // REPLACE or SUBTREE Modified
368 return resolveReplacedEvent(path, listeners, before, after.get());
371 // AFTER state is not present - child was deleted.
372 return resolveSameEventRecursivelly(path, listeners, before,
373 DOMImmutableDataChangeEvent.getRemoveEventFactory());
378 * Resolves create events deep down the interest listener tree.
386 private DOMImmutableDataChangeEvent resolveCreateEvent(final YangInstanceIdentifier path,
387 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
388 @SuppressWarnings({ "unchecked", "rawtypes" })
389 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
390 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
393 private DOMImmutableDataChangeEvent resolveDeleteEvent(final YangInstanceIdentifier path,
394 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
396 @SuppressWarnings({ "unchecked", "rawtypes" })
397 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
398 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
401 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final YangInstanceIdentifier path,
402 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
403 final SimpleEventFactory eventFactory) {
404 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
405 DOMImmutableDataChangeEvent propagateEvent = event;
406 // We have listeners for this node or it's children, so we will try
407 // to do additional processing
408 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
409 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
411 Builder eventBuilder = builder(DataChangeScope.BASE);
412 eventBuilder.merge(event);
413 eventBuilder.setBefore(event.getOriginalSubtree());
414 eventBuilder.setAfter(event.getUpdatedSubtree());
416 // Node has children, so we will try to resolve it's children
418 @SuppressWarnings("unchecked")
419 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
420 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
421 PathArgument childId = child.getIdentifier();
422 LOG.trace("Resolving event for child {}", childId);
423 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
424 eventBuilder.merge(resolveSameEventRecursivelly(path.node(childId), childListeners, child, eventFactory));
426 propagateEvent = eventBuilder.build();
428 if (!listeners.isEmpty()) {
429 addPartialTask(listeners, propagateEvent);
431 return propagateEvent;
434 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final YangInstanceIdentifier path,
435 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
437 Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
438 Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
440 Builder one = builder(DataChangeScope.ONE).
441 setBefore(modification.getDataBefore().get()).
442 setAfter(modification.getDataAfter().get());
443 Builder subtree = builder(DataChangeScope.SUBTREE).
444 setBefore(modification.getDataBefore().get()).
445 setAfter(modification.getDataAfter().get());
446 boolean oneModified = false;
447 for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
448 PathArgument childId = childMod.getIdentifier();
449 YangInstanceIdentifier childPath = path.node(childId);
450 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
453 switch (childMod.getModificationType()) {
457 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
460 case SUBTREE_MODIFIED:
461 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
468 final DOMImmutableDataChangeEvent oneChangeEvent;
470 one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
471 oneChangeEvent = one.build();
472 subtree.merge(oneChangeEvent);
474 oneChangeEvent = null;
475 subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
477 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
478 if (!listeners.isEmpty()) {
479 if(oneChangeEvent != null) {
480 addPartialTask(listeners, oneChangeEvent);
482 addPartialTask(listeners, subtreeEvent);
487 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
488 final DOMImmutableDataChangeEvent event) {
489 for (ListenerTree.Node listenerNode : listeners) {
490 if (!listenerNode.getListeners().isEmpty()) {
491 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
492 events.put(listenerNode, event);
498 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
499 final PathArgument child) {
500 if (parentNodes.isEmpty()) {
501 return Collections.emptyList();
503 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
504 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
505 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
506 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
508 addChildrenNodesToBuilder(result, parentNodes, child);
509 return result.build();
512 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
513 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
514 for (ListenerTree.Node node : parentNodes) {
515 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
516 if (child.isPresent()) {
517 result.add(child.get());
522 public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
523 return new ResolveDataChangeEventsTask(candidate, listenerTree);