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