BUG - 1756
[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.ArrayListMultimap;
13 import com.google.common.collect.Multimap;
14
15 import java.util.Collection;
16 import java.util.Map.Entry;
17
18 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
19 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
20 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
21 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
22 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
23 import org.opendaylight.yangtools.util.concurrent.NotificationManager;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
27 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
28 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Resolve Data Change Events based on modifications and listeners
35  *
36  * Computes data change events for all affected registered listeners in data
37  * tree.
38  */
39 final class ResolveDataChangeEventsTask {
40     private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
41
42     private final DataTreeCandidate candidate;
43     private final ListenerTree listenerRoot;
44
45     private Multimap<DataChangeListenerRegistration<?>, DOMImmutableDataChangeEvent> collectedEvents;
46
47     public ResolveDataChangeEventsTask(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
48         this.candidate = Preconditions.checkNotNull(candidate);
49         this.listenerRoot = Preconditions.checkNotNull(listenerTree);
50     }
51
52     /**
53      * Resolves and submits notification tasks to the specified manager.
54      */
55     public synchronized void resolve(final NotificationManager<DataChangeListenerRegistration<?>, DOMImmutableDataChangeEvent> manager) {
56         try (final Walker w = listenerRoot.getWalker()) {
57             // Defensive: reset internal state
58             collectedEvents = ArrayListMultimap.create();
59
60             // Run through the tree
61             final ResolveDataChangeState s = ResolveDataChangeState.initial(candidate.getRootPath(), w.getRootNode());
62             resolveAnyChangeEvent(s, candidate.getRootNode());
63
64             /*
65              * Convert to tasks, but be mindful of multiple values -- those indicate multiple
66              * wildcard matches, which need to be merged.
67              */
68             for (Entry<DataChangeListenerRegistration<?>, Collection<DOMImmutableDataChangeEvent>> e : collectedEvents.asMap().entrySet()) {
69                 final Collection<DOMImmutableDataChangeEvent> col = e.getValue();
70                 final DOMImmutableDataChangeEvent event;
71
72                 if (col.size() != 1) {
73                     final Builder b = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE);
74                     for (DOMImmutableDataChangeEvent i : col) {
75                         b.merge(i);
76                     }
77
78                     event = b.build();
79                     LOG.trace("Merged events {} into event {}", col, event);
80                 } else {
81                     event = col.iterator().next();
82                 }
83
84                 manager.submitNotification(e.getKey(), event);
85             }
86         }
87     }
88
89     /**
90      * Resolves data change event for supplied node
91      *
92      * @param path
93      *            Path to current node in tree
94      * @param listeners
95      *            Collection of Listener registration nodes interested in
96      *            subtree
97      * @param modification
98      *            Modification of current node
99      * @param before
100      *            - Original (before) state of current node
101      * @param after
102      *            - After state of current node
103      * @return True if the subtree changed, false otherwise
104      */
105     private boolean resolveAnyChangeEvent(final ResolveDataChangeState state, final DataTreeCandidateNode node) {
106         if (node.getModificationType() != ModificationType.UNMODIFIED &&
107                 !node.getDataAfter().isPresent() && !node.getDataBefore().isPresent()) {
108             LOG.debug("Modification at {} has type {}, but no before- and after-data. Assuming unchanged.",
109                     state.getPath(), node.getModificationType());
110             return false;
111         }
112
113         // no before and after state is present
114
115         switch (node.getModificationType()) {
116         case SUBTREE_MODIFIED:
117             return resolveSubtreeChangeEvent(state, node);
118         case MERGE:
119         case WRITE:
120             Preconditions.checkArgument(node.getDataAfter().isPresent(),
121                     "Modification at {} has type {} but no after-data", state.getPath(), node.getModificationType());
122             if (!node.getDataBefore().isPresent()) {
123                 resolveCreateEvent(state, node.getDataAfter().get());
124                 return true;
125             }
126
127             return resolveReplacedEvent(state, node.getDataBefore().get(), node.getDataAfter().get());
128         case DELETE:
129             Preconditions.checkArgument(node.getDataBefore().isPresent(),
130                     "Modification at {} has type {} but no before-data", state.getPath(), node.getModificationType());
131             resolveDeleteEvent(state, node.getDataBefore().get());
132             return true;
133         case UNMODIFIED:
134             return false;
135         }
136
137         throw new IllegalStateException(String.format("Unhandled node state %s at %s", node.getModificationType(), state.getPath()));
138     }
139
140     private boolean resolveReplacedEvent(final ResolveDataChangeState state,
141             final NormalizedNode<?, ?> beforeData, final NormalizedNode<?, ?> afterData) {
142
143         if (beforeData instanceof NormalizedNodeContainer<?, ?, ?>) {
144             /*
145              * Node is a container (contains a child) and we have interested
146              * listeners registered for it, that means we need to do
147              * resolution of changes on children level and can not
148              * shortcut resolution.
149              */
150             LOG.trace("Resolving subtree replace event for {} before {}, after {}", state.getPath(), beforeData, afterData);
151             @SuppressWarnings("unchecked")
152             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
153             @SuppressWarnings("unchecked")
154             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
155             return resolveNodeContainerReplaced(state, beforeCont, afterCont);
156         }
157
158         // Node is a Leaf type (does not contain child nodes)
159         // so normal equals method is sufficient for determining change.
160         if (beforeData.equals(afterData)) {
161             LOG.trace("Skipping equal leaf {}", state.getPath());
162             return false;
163         }
164
165         LOG.trace("Resolving leaf replace event for {} , before {}, after {}", state.getPath(), beforeData, afterData);
166         DOMImmutableDataChangeEvent event = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE).addUpdated(state.getPath(), beforeData, afterData).build();
167         state.addEvent(event);
168         state.collectEvents(beforeData, afterData, collectedEvents);
169         return true;
170     }
171
172     private boolean resolveNodeContainerReplaced(final ResolveDataChangeState state,
173             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
174                     final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
175         if (!state.needsProcessing()) {
176             LOG.trace("Not processing replaced container {}", state.getPath());
177             return true;
178         }
179
180         // We look at all children from before and compare it with after state.
181         boolean childChanged = false;
182         for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
183             final PathArgument childId = beforeChild.getIdentifier();
184
185             if (resolveNodeContainerChildUpdated(state.child(childId), beforeChild, afterCont.getChild(childId))) {
186                 childChanged = true;
187             }
188         }
189
190         for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
191             final PathArgument childId = afterChild.getIdentifier();
192
193             /*
194              * We have already iterated of the before-children, so have already
195              * emitted modify/delete events. This means the child has been
196              * created.
197              */
198             if (!beforeCont.getChild(childId).isPresent()) {
199                 resolveSameEventRecursivelly(state.child(childId), afterChild, DOMImmutableDataChangeEvent.getCreateEventFactory());
200                 childChanged = true;
201             }
202         }
203
204         if (childChanged) {
205             DOMImmutableDataChangeEvent event = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE)
206                     .addUpdated(state.getPath(), beforeCont, afterCont).build();
207             state.addEvent(event);
208         }
209
210         state.collectEvents(beforeCont, afterCont, collectedEvents);
211         return childChanged;
212     }
213
214     private boolean resolveNodeContainerChildUpdated(final ResolveDataChangeState state,
215             final NormalizedNode<PathArgument, ?> before, final Optional<NormalizedNode<PathArgument, ?>> after) {
216         if (after.isPresent()) {
217             // REPLACE or SUBTREE Modified
218             return resolveReplacedEvent(state, before, after.get());
219         }
220
221         // AFTER state is not present - child was deleted.
222         resolveSameEventRecursivelly(state, before, DOMImmutableDataChangeEvent.getRemoveEventFactory());
223         return true;
224     }
225
226     /**
227      * Resolves create events deep down the interest listener tree.
228      *
229      * @param path
230      * @param listeners
231      * @param afterState
232      * @return
233      */
234     private void resolveCreateEvent(final ResolveDataChangeState state, final NormalizedNode<?, ?> afterState) {
235         @SuppressWarnings({ "unchecked", "rawtypes" })
236         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState;
237         resolveSameEventRecursivelly(state, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
238     }
239
240     private void resolveDeleteEvent(final ResolveDataChangeState state, final NormalizedNode<?, ?> beforeState) {
241         @SuppressWarnings({ "unchecked", "rawtypes" })
242         final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState;
243         resolveSameEventRecursivelly(state, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
244     }
245
246     private void resolveSameEventRecursivelly(final ResolveDataChangeState state,
247             final NormalizedNode<PathArgument, ?> node, final SimpleEventFactory eventFactory) {
248         if (!state.needsProcessing()) {
249             LOG.trace("Skipping child {}", state.getPath());
250             return;
251         }
252
253         // We have listeners for this node or it's children, so we will try
254         // to do additional processing
255         if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
256             LOG.trace("Resolving subtree recursive event for {}, type {}", state.getPath(), eventFactory);
257
258             // Node has children, so we will try to resolve it's children
259             // changes.
260             @SuppressWarnings("unchecked")
261             NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
262             for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
263                 final PathArgument childId = child.getIdentifier();
264
265                 LOG.trace("Resolving event for child {}", childId);
266                 resolveSameEventRecursivelly(state.child(childId), child, eventFactory);
267             }
268         }
269
270         final DOMImmutableDataChangeEvent event = eventFactory.create(state.getPath(), node);
271         LOG.trace("Adding event {} at path {}", event, state.getPath());
272         state.addEvent(event);
273         state.collectEvents(event.getOriginalSubtree(), event.getUpdatedSubtree(), collectedEvents);
274     }
275
276     private boolean resolveSubtreeChangeEvent(final ResolveDataChangeState state, final DataTreeCandidateNode modification) {
277         Preconditions.checkArgument(modification.getDataBefore().isPresent(), "Subtree change with before-data not present at path %s", state.getPath());
278         Preconditions.checkArgument(modification.getDataAfter().isPresent(), "Subtree change with after-data not present at path %s", state.getPath());
279
280         DataChangeScope scope = null;
281         for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
282             final ResolveDataChangeState childState = state.child(childMod.getIdentifier());
283
284             switch (childMod.getModificationType()) {
285             case WRITE:
286             case MERGE:
287             case DELETE:
288                 if (resolveAnyChangeEvent(childState, childMod)) {
289                     scope = DataChangeScope.ONE;
290                 }
291                 break;
292             case SUBTREE_MODIFIED:
293                 if (resolveSubtreeChangeEvent(childState, childMod) && scope == null) {
294                     scope = DataChangeScope.SUBTREE;
295                 }
296                 break;
297             case UNMODIFIED:
298                 // no-op
299                 break;
300             }
301         }
302
303         final NormalizedNode<?, ?> before = modification.getDataBefore().get();
304         final NormalizedNode<?, ?> after = modification.getDataAfter().get();
305
306         if (scope != null) {
307             DOMImmutableDataChangeEvent one = DOMImmutableDataChangeEvent.builder(scope).addUpdated(state.getPath(), before, after).build();
308             state.addEvent(one);
309         }
310
311         state.collectEvents(before, after, collectedEvents);
312         return scope != null;
313     }
314
315     public static ResolveDataChangeEventsTask create(final DataTreeCandidate candidate, final ListenerTree listenerTree) {
316         return new ResolveDataChangeEventsTask(candidate, listenerTree);
317     }
318 }