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