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