Merge "Bug-590: Packet loss on first time ping test"
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / ResolveDataChangeEventsTask.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.md.sal.dom.store.impl;
9
10 import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
11 import static org.opendaylight.controller.md.sal.dom.store.impl.StoreUtils.append;
12
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;
19 import java.util.Set;
20 import java.util.concurrent.Callable;
21
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.ListenerTree;
26 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
27 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
28 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
29 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.common.base.Optional;
41 import com.google.common.collect.HashMultimap;
42 import com.google.common.collect.ImmutableList;
43 import com.google.common.collect.Iterables;
44 import com.google.common.collect.Multimap;
45
46 /**
47  *
48  * Resolve Data Change Events based on modifications and listeners
49  *
50  * Computes data change events for all affected registered listeners in data
51  * tree.
52  *
53  * Prerequisites for computation is to set all parameters properly:
54  * <ul>
55  * <li>{@link #setRootPath(InstanceIdentifier)} - Root path of datastore
56  * <li>{@link #setListenerRoot(ListenerTree)} - Root of listener registration
57  * tree, which contains listeners to be notified
58  * <li>{@link #setModificationRoot(NodeModification)} - Modification root, for
59  * which events should be computed
60  * <li>{@link #setBeforeRoot(Optional)} - State of before modification occurred
61  * <li>{@link #setAfterRoot(Optional)} - State of after modification occurred
62  * </ul>
63  *
64  */
65 public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
66     private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
67     private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
68
69     private InstanceIdentifier rootPath;
70     private ListenerTree listenerRoot;
71     private NodeModification modificationRoot;
72     private Optional<StoreMetadataNode> beforeRoot;
73     private Optional<StoreMetadataNode> afterRoot;
74     private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
75
76     protected InstanceIdentifier getRootPath() {
77         return rootPath;
78     }
79
80     protected ResolveDataChangeEventsTask setRootPath(final InstanceIdentifier rootPath) {
81         this.rootPath = rootPath;
82         return this;
83     }
84
85     protected ListenerTree getListenerRoot() {
86         return listenerRoot;
87     }
88
89     protected ResolveDataChangeEventsTask setListenerRoot(final ListenerTree listenerRoot) {
90         this.listenerRoot = listenerRoot;
91         return this;
92     }
93
94     protected NodeModification getModificationRoot() {
95         return modificationRoot;
96     }
97
98     protected ResolveDataChangeEventsTask setModificationRoot(final NodeModification modificationRoot) {
99         this.modificationRoot = modificationRoot;
100         return this;
101     }
102
103     protected Optional<StoreMetadataNode> getBeforeRoot() {
104         return beforeRoot;
105     }
106
107     protected ResolveDataChangeEventsTask setBeforeRoot(final Optional<StoreMetadataNode> beforeRoot) {
108         this.beforeRoot = beforeRoot;
109         return this;
110     }
111
112     protected Optional<StoreMetadataNode> getAfterRoot() {
113         return afterRoot;
114     }
115
116     protected ResolveDataChangeEventsTask setAfterRoot(final Optional<StoreMetadataNode> afterRoot) {
117         this.afterRoot = afterRoot;
118         return this;
119     }
120
121     /**
122      * Resolves and creates Notification Tasks
123      *
124      * Implementation of done as Map-Reduce with two steps: 1. resolving events
125      * and their mapping to listeners 2. merging events affecting same listener
126      *
127      * @return Iterable of Notification Tasks which needs to be executed in
128      *         order to delivery data change events.
129      */
130     @Override
131     public Iterable<ChangeListenerNotifyTask> call() {
132         LOG.trace("Resolving events for {}", modificationRoot);
133
134         try (final Walker w = listenerRoot.getWalker()) {
135             resolveAnyChangeEvent(rootPath, Collections.singleton(w.getRootNode()), modificationRoot, beforeRoot,
136                     afterRoot);
137             return createNotificationTasks();
138         }
139     }
140
141     /**
142      *
143      * Walks map of listeners to data change events, creates notification
144      * delivery tasks.
145      *
146      * Walks map of registered and affected listeners and creates notification
147      * tasks from set of listeners and events to be delivered.
148      *
149      * If set of listeners has more then one event (applicable to wildcarded
150      * listeners), merges all data change events into one, final which contains
151      * all separate updates.
152      *
153      * Dispatch between merge variant and reuse variant of notification task is
154      * done in
155      * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
156      *
157      * @return Collection of notification tasks.
158      */
159     private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
160         ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
161         for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
162             addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
163         }
164         return taskListBuilder.build();
165     }
166
167     /**
168      * Adds notification task to task list.
169      *
170      * If entry collection contains one event, this event is reused and added to
171      * notification tasks for listeners (see
172      * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
173      * . Otherwise events are merged by scope and distributed between listeners
174      * to particular scope. See
175      * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
176      * .
177      *
178      * @param taskListBuilder
179      * @param listeners
180      * @param entries
181      */
182     private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
183             final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
184
185         if (!entries.isEmpty()) {
186             if (entries.size() == 1) {
187                 addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
188             } else {
189                 addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
190             }
191         }
192     }
193
194     /**
195      *
196      * Add notification deliveries task to the listener.
197      *
198      *
199      * @param taskListBuilder
200      * @param listeners
201      * @param event
202      */
203     private static void addNotificationTaskByScope(
204             final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
205             final DOMImmutableDataChangeEvent event) {
206         DataChangeScope eventScope = event.getScope();
207         for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
208             DataChangeScope listenerScope = listenerReg.getScope();
209             List<DataChangeListenerRegistration<?>> listenerSet = Collections
210                     .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
211             if (eventScope == DataChangeScope.BASE) {
212                 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
213             } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
214                 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
215             } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
216                 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
217             }
218         }
219     }
220
221     /**
222      *
223      * Add notification tasks with merged event
224      *
225      * Separate Events by scope and creates merged notification tasks for each
226      * and every scope which is present.
227      *
228      * Adds merged events to task list based on scope requested by client.
229      *
230      * @param taskListBuilder
231      * @param listeners
232      * @param entries
233      */
234     private static void addNotificationTasksAndMergeEvents(
235             final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
236             final Collection<DOMImmutableDataChangeEvent> entries) {
237
238         final Builder baseBuilder = builder(DataChangeScope.BASE);
239         final Builder oneBuilder = builder(DataChangeScope.ONE);
240         final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
241
242         boolean baseModified = false;
243         boolean oneModified = false;
244         boolean subtreeModified = false;
245         for (final DOMImmutableDataChangeEvent entry : entries) {
246             switch (entry.getScope()) {
247             // Absence of breaks is intentional here. Subtree contains base and
248             // one, one also contains base
249             case BASE:
250                 baseBuilder.merge(entry);
251                 baseModified = true;
252             case ONE:
253                 oneBuilder.merge(entry);
254                 oneModified = true;
255             case SUBTREE:
256                 subtreeBuilder.merge(entry);
257                 subtreeModified = true;
258             }
259         }
260
261         if (baseModified) {
262             addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
263         }
264         if (oneModified) {
265             addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
266         }
267         if (subtreeModified) {
268             addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
269         }
270     }
271
272     private static void addNotificationTaskExclusively(
273             final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
274             final DOMImmutableDataChangeEvent event) {
275         for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
276             if (listener.getScope() == event.getScope()) {
277                 Set<DataChangeListenerRegistration<?>> listenerSet = Collections
278                         .<DataChangeListenerRegistration<?>> singleton(listener);
279                 taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
280             }
281         }
282     }
283
284     /**
285      * Resolves data change event for supplied node
286      *
287      * @param path
288      *            Path to current node in tree
289      * @param listeners
290      *            Collection of Listener registration nodes interested in
291      *            subtree
292      * @param modification
293      *            Modification of current node
294      * @param before
295      *            - Original (before) state of current node
296      * @param after
297      *            - After state of current node
298      * @return Data Change Event of this node and all it's children
299      */
300     private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
301             final Collection<ListenerTree.Node> listeners, final NodeModification modification,
302             final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
303         // No listeners are present in listener registration subtree
304         // no before and after state is present
305         if (!before.isPresent() && !after.isPresent()) {
306             return NO_CHANGE;
307         }
308         switch (modification.getModificationType()) {
309         case SUBTREE_MODIFIED:
310             return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
311         case MERGE:
312         case WRITE:
313             if (before.isPresent()) {
314                 return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
315             } else {
316                 return resolveCreateEvent(path, listeners, after.get());
317             }
318         case DELETE:
319             return resolveDeleteEvent(path, listeners, before.get());
320         default:
321             return NO_CHANGE;
322         }
323
324     }
325
326     private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
327             final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
328             final NormalizedNode<?, ?> afterData) {
329
330         if (beforeData instanceof NormalizedNodeContainer<?, ?, ?> && !listeners.isEmpty()) {
331             // Node is container (contains child) and we have interested
332             // listeners registered for it, that means we need to do
333             // resolution of changes on children level and can not
334             // shortcut resolution.
335
336             @SuppressWarnings("unchecked")
337             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
338             @SuppressWarnings("unchecked")
339             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
340             return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
341         } else if (!beforeData.equals(afterData)) {
342             // Node is either of Leaf type (does not contain child nodes)
343             // or we do not have listeners, so normal equals method is
344             // sufficient for determining change.
345
346             DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
347                     .addUpdated(path, beforeData, afterData).build();
348             addPartialTask(listeners, event);
349             return event;
350         } else {
351             return NO_CHANGE;
352         }
353     }
354
355     private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
356             final Collection<Node> listeners,
357             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
358             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
359         final Set<PathArgument> alreadyProcessed = new HashSet<>();
360         final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
361
362         DataChangeScope potentialScope = DataChangeScope.BASE;
363         // We look at all children from before and compare it with after state.
364         for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
365             PathArgument childId = beforeChild.getIdentifier();
366             alreadyProcessed.add(childId);
367             InstanceIdentifier childPath = append(path, childId);
368             Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
369             Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
370             DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
371                     beforeChild, afterChild);
372             // If change is empty (equals to NO_CHANGE)
373             if (childChange != NO_CHANGE) {
374                 childChanges.add(childChange);
375             }
376
377         }
378
379         for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
380             PathArgument childId = afterChild.getIdentifier();
381             if (!alreadyProcessed.contains(childId)) {
382                 // We did not processed that child already
383                 // and it was not present in previous loop, that means it is
384                 // created.
385                 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
386                 InstanceIdentifier childPath = append(path,childId);
387                 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
388                         DOMImmutableDataChangeEvent.getCreateEventFactory()));
389             }
390         }
391         if (childChanges.isEmpty()) {
392             return NO_CHANGE;
393         }
394
395         Builder eventBuilder = builder(potentialScope) //
396                 .setBefore(beforeCont) //
397                 .setAfter(afterCont);
398         for (DOMImmutableDataChangeEvent childChange : childChanges) {
399             eventBuilder.merge(childChange);
400         }
401
402         DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
403         addPartialTask(listeners, replaceEvent);
404         return replaceEvent;
405     }
406
407     private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
408             final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
409             final Optional<NormalizedNode<PathArgument, ?>> after) {
410
411         if (after.isPresent()) {
412             // REPLACE or SUBTREE Modified
413             return resolveReplacedEvent(path, listeners, before, after.get());
414
415         } else {
416             // AFTER state is not present - child was deleted.
417             return resolveSameEventRecursivelly(path, listeners, before,
418                     DOMImmutableDataChangeEvent.getRemoveEventFactory());
419         }
420     }
421
422     /**
423      * Resolves create events deep down the interest listener tree.
424      *
425      *
426      * @param path
427      * @param listeners
428      * @param afterState
429      * @return
430      */
431     private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
432             final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
433         @SuppressWarnings({ "unchecked", "rawtypes" })
434         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
435         return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
436     }
437
438     private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
439             final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
440
441         @SuppressWarnings({ "unchecked", "rawtypes" })
442         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
443         return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
444     }
445
446     private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
447             final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
448             final SimpleEventFactory eventFactory) {
449
450         DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
451
452         if (!listeners.isEmpty()) {
453             // We have listeners for this node or it's children, so we will try
454             // to do additional processing
455             if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
456                 // Node has children, so we will try to resolve it's children
457                 // changes.
458                 @SuppressWarnings("unchecked")
459                 NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
460                 for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
461                     PathArgument childId = child.getIdentifier();
462                     Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
463                     if (!childListeners.isEmpty()) {
464                         resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory);
465                     }
466                 }
467             }
468             addPartialTask(listeners, event);
469         }
470         return event;
471     }
472
473     private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
474             final Collection<ListenerTree.Node> listeners, final NodeModification modification,
475             final StoreMetadataNode before, final StoreMetadataNode after) {
476
477         Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
478
479         Builder subtree = builder(DataChangeScope.SUBTREE);
480
481         for (NodeModification childMod : modification.getModifications()) {
482             PathArgument childId = childMod.getIdentifier();
483             InstanceIdentifier childPath = append(path, childId);
484             Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
485
486             Optional<StoreMetadataNode> childBefore = before.getChild(childId);
487             Optional<StoreMetadataNode> childAfter = after.getChild(childId);
488
489             switch (childMod.getModificationType()) {
490             case WRITE:
491             case MERGE:
492             case DELETE:
493                 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
494                 break;
495             case SUBTREE_MODIFIED:
496                 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
497                         childAfter.get()));
498                 break;
499             case UNMODIFIED:
500                 // no-op
501                 break;
502             }
503         }
504         DOMImmutableDataChangeEvent oneChangeEvent = one.build();
505         subtree.merge(oneChangeEvent);
506         DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
507         if (!listeners.isEmpty()) {
508             addPartialTask(listeners, oneChangeEvent);
509             addPartialTask(listeners, subtreeEvent);
510         }
511         return subtreeEvent;
512     }
513
514     private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
515             final DOMImmutableDataChangeEvent event) {
516
517         for (ListenerTree.Node listenerNode : listeners) {
518             if (!listenerNode.getListeners().isEmpty()) {
519                 events.put(listenerNode, event);
520             }
521         }
522         return event;
523     }
524
525     private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
526             final PathArgument child) {
527         if (parentNodes.isEmpty()) {
528             return Collections.emptyList();
529         }
530         com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
531         if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
532             NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
533             addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
534         }
535         addChildrenNodesToBuilder(result, parentNodes, child);
536         return result.build();
537     }
538
539     private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
540             final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
541         for (ListenerTree.Node node : parentNodes) {
542             Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
543             if (child.isPresent()) {
544                 result.add(child.get());
545             }
546         }
547     }
548
549     public static ResolveDataChangeEventsTask create() {
550         return new ResolveDataChangeEventsTask();
551     }
552 }