1 package org.opendaylight.controller.md.sal.dom.store.impl;
3 import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
4 import static org.opendaylight.controller.md.sal.dom.store.impl.StoreUtils.append;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.HashSet;
9 import java.util.LinkedList;
10 import java.util.List;
11 import java.util.Map.Entry;
13 import java.util.concurrent.Callable;
15 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
16 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
17 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
18 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
19 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
20 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
21 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
22 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
23 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
26 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 import com.google.common.base.Optional;
34 import com.google.common.collect.HashMultimap;
35 import com.google.common.collect.ImmutableList;
36 import com.google.common.collect.Iterables;
37 import com.google.common.collect.Multimap;
41 * Resolve Data Change Events based on modifications and listeners
43 * Computes data change events for all affected registered listeners in data
46 * Prerequisites for computation is to set all parameters properly:
48 * <li>{@link #setRootPath(InstanceIdentifier)} - Root path of datastore
49 * <li>{@link #setListenerRoot(ListenerTree)} - Root of listener registration
50 * tree, which contains listeners to be notified
51 * <li>{@link #setModificationRoot(NodeModification)} - Modification root, for
52 * which events should be computed
53 * <li>{@link #setBeforeRoot(Optional)} - State of before modification occurred
54 * <li>{@link #setAfterRoot(Optional)} - State of after modification occurred
58 public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
59 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
60 private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
62 private InstanceIdentifier rootPath;
63 private ListenerTree listenerRoot;
64 private NodeModification modificationRoot;
65 private Optional<StoreMetadataNode> beforeRoot;
66 private Optional<StoreMetadataNode> afterRoot;
67 private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
69 protected InstanceIdentifier getRootPath() {
73 protected ResolveDataChangeEventsTask setRootPath(final InstanceIdentifier rootPath) {
74 this.rootPath = rootPath;
78 protected ListenerTree getListenerRoot() {
82 protected ResolveDataChangeEventsTask setListenerRoot(final ListenerTree listenerRoot) {
83 this.listenerRoot = listenerRoot;
87 protected NodeModification getModificationRoot() {
88 return modificationRoot;
91 protected ResolveDataChangeEventsTask setModificationRoot(final NodeModification modificationRoot) {
92 this.modificationRoot = modificationRoot;
96 protected Optional<StoreMetadataNode> getBeforeRoot() {
100 protected ResolveDataChangeEventsTask setBeforeRoot(final Optional<StoreMetadataNode> beforeRoot) {
101 this.beforeRoot = beforeRoot;
105 protected Optional<StoreMetadataNode> getAfterRoot() {
109 protected ResolveDataChangeEventsTask setAfterRoot(final Optional<StoreMetadataNode> afterRoot) {
110 this.afterRoot = afterRoot;
115 * Resolves and creates Notification Tasks
117 * Implementation of done as Map-Reduce with two steps: 1. resolving events
118 * and their mapping to listeners 2. merging events affecting same listener
120 * @return Iterable of Notification Tasks which needs to be executed in
121 * order to delivery data change events.
124 public Iterable<ChangeListenerNotifyTask> call() {
125 LOG.trace("Resolving events for {}", modificationRoot);
127 try (final Walker w = listenerRoot.getWalker()) {
128 resolveAnyChangeEvent(rootPath, Collections.singleton(w.getRootNode()), modificationRoot, beforeRoot,
130 return createNotificationTasks();
136 * Walks map of listeners to data change events, creates notification
139 * Walks map of registered and affected listeners and creates notification
140 * tasks from set of listeners and events to be delivered.
142 * If set of listeners has more then one event (applicable to wildcarded
143 * listeners), merges all data change events into one, final which contains
144 * all separate updates.
146 * Dispatch between merge variant and reuse variant of notification task is
148 * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
150 * @return Collection of notification tasks.
152 private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
153 ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
154 for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
155 addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
157 return taskListBuilder.build();
161 * Adds notification task to task list.
163 * If entry collection contains one event, this event is reused and added to
164 * notification tasks for listeners (see
165 * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
166 * . Otherwise events are merged by scope and distributed between listeners
167 * to particular scope. See
168 * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
171 * @param taskListBuilder
175 private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
176 final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
178 if (!entries.isEmpty()) {
179 if (entries.size() == 1) {
180 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
182 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
189 * Add notification deliveries task to the listener.
192 * @param taskListBuilder
196 private static void addNotificationTaskByScope(
197 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
198 final DOMImmutableDataChangeEvent event) {
199 DataChangeScope eventScope = event.getScope();
200 for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
201 DataChangeScope listenerScope = listenerReg.getScope();
202 List<DataChangeListenerRegistration<?>> listenerSet = Collections
203 .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
204 if (eventScope == DataChangeScope.BASE) {
205 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
206 } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
207 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
208 } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
209 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
216 * Add notification tasks with merged event
218 * Separate Events by scope and creates merged notification tasks for each
219 * and every scope which is present.
221 * Adds merged events to task list based on scope requested by client.
223 * @param taskListBuilder
227 private static void addNotificationTasksAndMergeEvents(
228 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
229 final Collection<DOMImmutableDataChangeEvent> entries) {
231 final Builder baseBuilder = builder(DataChangeScope.BASE);
232 final Builder oneBuilder = builder(DataChangeScope.ONE);
233 final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
235 boolean baseModified = false;
236 boolean oneModified = false;
237 boolean subtreeModified = false;
238 for (final DOMImmutableDataChangeEvent entry : entries) {
239 switch (entry.getScope()) {
240 // Absence of breaks is intentional here. Subtree contains base and
241 // one, one also contains base
243 baseBuilder.merge(entry);
246 oneBuilder.merge(entry);
249 subtreeBuilder.merge(entry);
250 subtreeModified = true;
255 addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
258 addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
260 if (subtreeModified) {
261 addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
265 private static void addNotificationTaskExclusively(
266 final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
267 final DOMImmutableDataChangeEvent event) {
268 for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
269 if (listener.getScope() == event.getScope()) {
270 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
271 .<DataChangeListenerRegistration<?>> singleton(listener);
272 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
278 * Resolves data change event for supplied node
281 * Path to current node in tree
283 * Collection of Listener registration nodes interested in
285 * @param modification
286 * Modification of current node
288 * - Original (before) state of current node
290 * - After state of current node
291 * @return Data Change Event of this node and all it's children
293 private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
294 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
295 final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
296 // No listeners are present in listener registration subtree
297 // no before and after state is present
298 if (!before.isPresent() && !after.isPresent()) {
301 switch (modification.getModificationType()) {
302 case SUBTREE_MODIFIED:
303 return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
305 if (before.isPresent()) {
306 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
308 return resolveCreateEvent(path, listeners, after.get());
311 return resolveDeleteEvent(path, listeners, before.get());
318 private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
319 final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
320 final NormalizedNode<?, ?> afterData) {
322 if (beforeData instanceof NormalizedNodeContainer<?, ?, ?> && !listeners.isEmpty()) {
323 // Node is container (contains child) and we have interested
324 // listeners registered for it, that means we need to do
325 // resolution of changes on children level and can not
326 // shortcut resolution.
328 @SuppressWarnings("unchecked")
329 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
330 @SuppressWarnings("unchecked")
331 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
332 return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
333 } else if (!beforeData.equals(afterData)) {
334 // Node is either of Leaf type (does not contain child nodes)
335 // or we do not have listeners, so normal equals method is
336 // sufficient for determining change.
338 DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
339 .addUpdated(path, beforeData, afterData).build();
340 addPartialTask(listeners, event);
347 private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
348 final Collection<Node> listeners,
349 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
350 final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
351 final Set<PathArgument> alreadyProcessed = new HashSet<>();
352 final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
354 DataChangeScope potentialScope = DataChangeScope.BASE;
355 // We look at all children from before and compare it with after state.
356 for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
357 PathArgument childId = beforeChild.getIdentifier();
358 alreadyProcessed.add(childId);
359 InstanceIdentifier childPath = append(path, childId);
360 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
361 Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
362 DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
363 beforeChild, afterChild);
364 // If change is empty (equals to NO_CHANGE)
365 if (childChange != NO_CHANGE) {
366 childChanges.add(childChange);
371 for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
372 PathArgument childId = afterChild.getIdentifier();
373 if (!alreadyProcessed.contains(childId)) {
374 // We did not processed that child already
375 // and it was not present in previous loop, that means it is
377 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
378 InstanceIdentifier childPath = append(path,childId);
379 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
380 DOMImmutableDataChangeEvent.getCreateEventFactory()));
383 if (childChanges.isEmpty()) {
387 Builder eventBuilder = builder(potentialScope) //
388 .setBefore(beforeCont) //
389 .setAfter(afterCont);
390 for (DOMImmutableDataChangeEvent childChange : childChanges) {
391 eventBuilder.merge(childChange);
394 DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
395 addPartialTask(listeners, replaceEvent);
399 private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
400 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
401 final Optional<NormalizedNode<PathArgument, ?>> after) {
403 if (after.isPresent()) {
404 // REPLACE or SUBTREE Modified
405 return resolveReplacedEvent(path, listeners, before, after.get());
408 // AFTER state is not present - child was deleted.
409 return resolveSameEventRecursivelly(path, listeners, before,
410 DOMImmutableDataChangeEvent.getRemoveEventFactory());
415 * Resolves create events deep down the interest listener tree.
423 private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
424 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
425 @SuppressWarnings({ "unchecked", "rawtypes" })
426 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
427 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
430 private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
431 final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
433 @SuppressWarnings({ "unchecked", "rawtypes" })
434 final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
435 return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
438 private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
439 final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
440 final SimpleEventFactory eventFactory) {
442 DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
444 if (!listeners.isEmpty()) {
445 // We have listeners for this node or it's children, so we will try
446 // to do additional processing
447 if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
448 // Node has children, so we will try to resolve it's children
450 @SuppressWarnings("unchecked")
451 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
452 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
453 PathArgument childId = child.getIdentifier();
454 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
455 if (!childListeners.isEmpty()) {
456 resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory);
460 addPartialTask(listeners, event);
465 private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
466 final Collection<ListenerTree.Node> listeners, final NodeModification modification,
467 final StoreMetadataNode before, final StoreMetadataNode after) {
469 Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
471 Builder subtree = builder(DataChangeScope.SUBTREE);
473 for (NodeModification childMod : modification.getModifications()) {
474 PathArgument childId = childMod.getIdentifier();
475 InstanceIdentifier childPath = append(path, childId);
476 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
478 Optional<StoreMetadataNode> childBefore = before.getChild(childId);
479 Optional<StoreMetadataNode> childAfter = after.getChild(childId);
481 switch (childMod.getModificationType()) {
484 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
486 case SUBTREE_MODIFIED:
487 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
495 DOMImmutableDataChangeEvent oneChangeEvent = one.build();
496 subtree.merge(oneChangeEvent);
497 DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
498 if (!listeners.isEmpty()) {
499 addPartialTask(listeners, oneChangeEvent);
500 addPartialTask(listeners, subtreeEvent);
505 private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
506 final DOMImmutableDataChangeEvent event) {
508 for (ListenerTree.Node listenerNode : listeners) {
509 if (!listenerNode.getListeners().isEmpty()) {
510 events.put(listenerNode, event);
516 private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
517 final PathArgument child) {
518 if (parentNodes.isEmpty()) {
519 return Collections.emptyList();
521 com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
522 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
523 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
524 addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
526 addChildrenNodesToBuilder(result, parentNodes, child);
527 return result.build();
530 private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
531 final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
532 for (ListenerTree.Node node : parentNodes) {
533 Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
534 if (child.isPresent()) {
535 result.add(child.get());
540 public static ResolveDataChangeEventsTask create() {
541 return new ResolveDataChangeEventsTask();