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 com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.HashMultimap;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.Iterables;
15 import com.google.common.collect.Multimap;
16 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
17 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
18 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
19 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
20 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
21 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
22 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
25 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
26 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
29 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
30 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashSet;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Map.Entry;
42 import java.util.concurrent.Callable;
44 import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
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 InstanceIdentifier 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 InstanceIdentifier path,
277 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
278 final NormalizedNode<?, ?> afterData) {
280 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
281 // Node is container (contains child) and we have interested
282 // listeners registered for it, that means we need to do
283 // resolution of changes on children level and can not
284 // shortcut resolution.
285 LOG.trace("Resolving subtree replace event for {} before {}, after {}",path,beforeData,afterData);
286 @SuppressWarnings("unchecked")
287 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
288 @SuppressWarnings("unchecked")
289 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
290 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
291 } else if (!beforeData.equals(afterData)) {
292 // Node is Leaf type (does not contain child nodes)
293 // so normal equals method is sufficient for determining change.
294 LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
295 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
296 .addUpdated(path, beforeData, afterData).build();
297 addPartialTask(listeners, event);
304 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
305 final Collection<Node> listeners,
306 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
307 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
308 final Set<PathArgument> alreadyProcessed = new HashSet<>();
309 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
311 DataChangeScope potentialScope = DataChangeScope.BASE;
312 // We look at all children from before and compare it with after state.
313 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
314 PathArgument childId = beforeChild.getIdentifier();
315 alreadyProcessed.add(childId);
316 InstanceIdentifier childPath = path.node(childId);
317 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
318 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
319 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
320 beforeChild, afterChild);
321 // If change is empty (equals to NO_CHANGE)
322 if (childChange != NO_CHANGE) {
323 childChanges.add(childChange);
328 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
329 PathArgument childId = afterChild.getIdentifier();
330 if (!alreadyProcessed.contains(childId)) {
331 // We did not processed that child already
332 // and it was not present in previous loop, that means it is
334 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
335 InstanceIdentifier childPath = path.node(childId);
336 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
337 DOMImmutableDataChangeEvent.getCreateEventFactory()));
340 if (childChanges.isEmpty()) {
344 Builder eventBuilder = builder(potentialScope) //
345 .setBefore(beforeCont) //
347 .addUpdated(path, beforeCont, afterCont);
348 for (DOMImmutableDataChangeEvent childChange : childChanges) {
349 eventBuilder.merge(childChange);
352 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
353 addPartialTask(listeners, replaceEvent);
357 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
358 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
359 final Optional<NormalizedNode<PathArgument, ?>> after) {
361 if (after.isPresent()) {
362 // REPLACE or SUBTREE Modified
363 return resolveReplacedEvent(path, listeners, before, after.get());
366 // AFTER state is not present - child was deleted.
367 return resolveSameEventRecursivelly(path, listeners, before,
368 DOMImmutableDataChangeEvent.getRemoveEventFactory());
373 * Resolves create events deep down the interest listener tree.
381 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
382 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
383 @SuppressWarnings({ "unchecked", "rawtypes" })
384 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
385 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
388 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
389 final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
391 @SuppressWarnings({ "unchecked", "rawtypes" })
392 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
393 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
396 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
397 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
398 final SimpleEventFactory eventFactory) {
399 final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
400 DOMImmutableDataChangeEvent propagateEvent = event;
401 // We have listeners for this node or it's children, so we will try
402 // to do additional processing
403 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
404 LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
406 Builder eventBuilder = builder(DataChangeScope.BASE);
407 eventBuilder.merge(event);
408 eventBuilder.setBefore(event.getOriginalSubtree());
409 eventBuilder.setAfter(event.getUpdatedSubtree());
411 // Node has children, so we will try to resolve it's children
413 @SuppressWarnings("unchecked")
414 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
415 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
416 PathArgument childId = child.getIdentifier();
417 LOG.trace("Resolving event for child {}", childId);
418 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
419 eventBuilder.merge(resolveSameEventRecursivelly(path.node(childId), childListeners, child, eventFactory));
421 propagateEvent = eventBuilder.build();
423 if (!listeners.isEmpty()) {
424 addPartialTask(listeners, propagateEvent);
426 return propagateEvent;
429 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
430 final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
432 Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
433 Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
435 Builder one = builder(DataChangeScope.ONE).
436 setBefore(modification.getDataBefore().get()).
437 setAfter(modification.getDataAfter().get());
438 Builder subtree = builder(DataChangeScope.SUBTREE).
439 setBefore(modification.getDataBefore().get()).
440 setAfter(modification.getDataAfter().get());
442 for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
443 PathArgument childId = childMod.getIdentifier();
444 InstanceIdentifier childPath = path.node(childId);
445 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
447 switch (childMod.getModificationType()) {
451 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
453 case SUBTREE_MODIFIED:
454 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
461 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
462 subtree.merge(oneChangeEvent);
463 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
464 if (!listeners.isEmpty()) {
465 addPartialTask(listeners, oneChangeEvent);
466 addPartialTask(listeners, subtreeEvent);
471 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
472 final DOMImmutableDataChangeEvent event) {
473 for (ListenerTree.Node listenerNode : listeners) {
474 if (!listenerNode.getListeners().isEmpty()) {
475 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
476 events.put(listenerNode, event);
482 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
483 final PathArgument child) {
484 if (parentNodes.isEmpty()) {
485 return Collections.emptyList();
487 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
488 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
489 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
490 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
492 addChildrenNodesToBuilder(result, parentNodes, child);
493 return result.build();
496 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
497 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
498 for (ListenerTree.Node node : parentNodes) {
499 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
500 if (child.isPresent()) {
501 result.add(child.get());
506 public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
507 return new ResolveDataChangeEventsTask(candidate, listenerTree);