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