Merge "Implement PortNumberBuilder"
[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 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.ListenerTree;
25 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
26 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
35 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
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, java.util.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, java.util.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 Leaf type (does not contain child nodes)
294             // so normal equals method is sufficient for determining change.
295             LOG.trace("Resolving leaf replace event for {} , before {}, after {}",path,beforeData,afterData);
296             DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
297                     .addUpdated(path, beforeData, afterData).build();
298             addPartialTask(listeners, event);
299             return event;
300         } else {
301             return NO_CHANGE;
302         }
303     }
304
305     private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
306             final Collection<Node> listeners,
307             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
308                     final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
309         final Set<PathArgument> alreadyProcessed = new HashSet<>();
310         final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
311
312         DataChangeScope potentialScope = DataChangeScope.BASE;
313         // We look at all children from before and compare it with after state.
314         for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
315             PathArgument childId = beforeChild.getIdentifier();
316             alreadyProcessed.add(childId);
317             InstanceIdentifier childPath = path.node(childId);
318             Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
319             Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
320             DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
321                     beforeChild, afterChild);
322             // If change is empty (equals to NO_CHANGE)
323             if (childChange != NO_CHANGE) {
324                 childChanges.add(childChange);
325             }
326
327         }
328
329         for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
330             PathArgument childId = afterChild.getIdentifier();
331             if (!alreadyProcessed.contains(childId)) {
332                 // We did not processed that child already
333                 // and it was not present in previous loop, that means it is
334                 // created.
335                 Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
336                 InstanceIdentifier childPath = path.node(childId);
337                 childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
338                         DOMImmutableDataChangeEvent.getCreateEventFactory()));
339             }
340         }
341         if (childChanges.isEmpty()) {
342             return NO_CHANGE;
343         }
344
345         Builder eventBuilder = builder(potentialScope) //
346                 .setBefore(beforeCont) //
347                 .setAfter(afterCont)
348                 .addUpdated(path, beforeCont, afterCont);
349         for (DOMImmutableDataChangeEvent childChange : childChanges) {
350             eventBuilder.merge(childChange);
351         }
352
353         DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
354         addPartialTask(listeners, replaceEvent);
355         return replaceEvent;
356     }
357
358     private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
359             final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
360             final Optional<NormalizedNode<PathArgument, ?>> after) {
361
362         if (after.isPresent()) {
363             // REPLACE or SUBTREE Modified
364             return resolveReplacedEvent(path, listeners, before, after.get());
365
366         } else {
367             // AFTER state is not present - child was deleted.
368             return resolveSameEventRecursivelly(path, listeners, before,
369                     DOMImmutableDataChangeEvent.getRemoveEventFactory());
370         }
371     }
372
373     /**
374      * Resolves create events deep down the interest listener tree.
375      *
376      *
377      * @param path
378      * @param listeners
379      * @param afterState
380      * @return
381      */
382     private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
383             final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> afterState) {
384         @SuppressWarnings({ "unchecked", "rawtypes" })
385         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
386         return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
387     }
388
389     private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
390             final Collection<ListenerTree.Node> listeners, final NormalizedNode<?, ?> beforeState) {
391
392         @SuppressWarnings({ "unchecked", "rawtypes" })
393         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
394         return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
395     }
396
397     private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
398             final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
399             final SimpleEventFactory eventFactory) {
400         final DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
401         DOMImmutableDataChangeEvent propagateEvent = event;
402         // We have listeners for this node or it's children, so we will try
403         // to do additional processing
404         if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
405             LOG.trace("Resolving subtree recursive event for {}, type {}", path, eventFactory);
406
407             Builder eventBuilder = builder(DataChangeScope.BASE);
408             eventBuilder.merge(event);
409             eventBuilder.setBefore(event.getOriginalSubtree());
410             eventBuilder.setAfter(event.getUpdatedSubtree());
411
412             // Node has children, so we will try to resolve it's children
413             // changes.
414             @SuppressWarnings("unchecked")
415             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
416             for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
417                 PathArgument childId = child.getIdentifier();
418                 LOG.trace("Resolving event for child {}", childId);
419                 Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
420                 eventBuilder.merge(resolveSameEventRecursivelly(path.node(childId), childListeners, child, eventFactory));
421             }
422             propagateEvent = eventBuilder.build();
423         }
424         if (!listeners.isEmpty()) {
425             addPartialTask(listeners, propagateEvent);
426         }
427         return propagateEvent;
428     }
429
430     private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
431             final Collection<ListenerTree.Node> listeners, final DataTreeCandidateNode modification) {
432
433         Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", path);
434         Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", path);
435
436         Builder one = builder(DataChangeScope.ONE).
437                 setBefore(modification.getDataBefore().get()).
438                 setAfter(modification.getDataAfter().get());
439         Builder subtree = builder(DataChangeScope.SUBTREE).
440                 setBefore(modification.getDataBefore().get()).
441                 setAfter(modification.getDataAfter().get());
442         boolean oneModified = false;
443         for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
444             PathArgument childId = childMod.getIdentifier();
445             InstanceIdentifier childPath = path.node(childId);
446             Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
447
448
449             switch (childMod.getModificationType()) {
450             case WRITE:
451             case MERGE:
452             case DELETE:
453                 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
454                 oneModified = true;
455                 break;
456             case SUBTREE_MODIFIED:
457                 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
458                 break;
459             case UNMODIFIED:
460                 // no-op
461                 break;
462             }
463         }
464         final DOMImmutableDataChangeEvent oneChangeEvent;
465         if(oneModified) {
466             one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
467             oneChangeEvent = one.build();
468             subtree.merge(oneChangeEvent);
469         } else {
470             oneChangeEvent = null;
471             subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
472         }
473         DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
474         if (!listeners.isEmpty()) {
475             if(oneChangeEvent != null) {
476                 addPartialTask(listeners, oneChangeEvent);
477             }
478             addPartialTask(listeners, subtreeEvent);
479         }
480         return subtreeEvent;
481     }
482
483     private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
484             final DOMImmutableDataChangeEvent event) {
485         for (ListenerTree.Node listenerNode : listeners) {
486             if (!listenerNode.getListeners().isEmpty()) {
487                 LOG.trace("Adding event {} for listeners {}",event,listenerNode);
488                 events.put(listenerNode, event);
489             }
490         }
491         return event;
492     }
493
494     private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
495             final PathArgument child) {
496         if (parentNodes.isEmpty()) {
497             return Collections.emptyList();
498         }
499         com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
500         if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
501             NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
502             addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
503         }
504         addChildrenNodesToBuilder(result, parentNodes, child);
505         return result.build();
506     }
507
508     private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
509             final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
510         for (ListenerTree.Node node : parentNodes) {
511             Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
512             if (child.isPresent()) {
513                 result.add(child.get());
514             }
515         }
516     }
517
518     public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
519         return new ResolveDataChangeEventsTask(candidate, listenerTree);
520     }
521 }