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