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