Fix AbstractDOMShardTreeChangePublisher's initial data change event
[mdsal.git] / dom / mdsal-dom-inmemory-datastore / src / main / java / org / opendaylight / mdsal / dom / store / inmemory / AbstractDOMShardTreeChangePublisher.java
1 /*
2  * Copyright (c) 2016 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
9 package org.opendaylight.mdsal.dom.store.inmemory;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.stream.Collectors;
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
25 import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
26 import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
27 import org.opendaylight.mdsal.dom.spi.store.AbstractDOMStoreTreeChangePublisher;
28 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
29 import org.opendaylight.yangtools.concepts.ListenerRegistration;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
37 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
43 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher implements DOMStoreTreeChangePublisher {
50
51     private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
52
53     private final YangInstanceIdentifier shardPath;
54     private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
55     private final DataTree dataTree;
56
57     protected AbstractDOMShardTreeChangePublisher(final DataTree dataTree,
58                                                   final YangInstanceIdentifier shardPath,
59                                                   final Map<DOMDataTreeIdentifier, ChildShardContext> childShards) {
60         this.dataTree = Preconditions.checkNotNull(dataTree);
61         this.shardPath = Preconditions.checkNotNull(shardPath);
62         this.childShards = Preconditions.checkNotNull(childShards);
63     }
64
65     @Override
66     public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
67         takeLock();
68         try {
69             return setupListenerContext(path, listener);
70         } finally {
71             releaseLock();
72         }
73     }
74
75     private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
76         // we need to register the listener registration path based on the shards root
77         // we have to strip the shard path from the listener path and then register
78         YangInstanceIdentifier strippedIdentifier = listenerPath;
79         if (!shardPath.isEmpty()) {
80             strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
81         }
82
83         final DOMDataTreeListenerWithSubshards subshardListener = new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
84         final AbstractDOMDataTreeChangeListenerRegistration<L> reg = setupContextWithoutSubshards(strippedIdentifier, subshardListener);
85
86         for (final ChildShardContext maybeAffected : childShards.values()) {
87             if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
88                 // consumer has initialDataChangeEvent subshard somewhere on lower level
89                 // register to the notification manager with snapshot and forward child notifications to parent
90                 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
91                 subshardListener.addSubshard(maybeAffected);
92             } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
93                 // bind path is inside subshard
94                 // TODO can this happen? seems like in ShardedDOMDataTree we are already registering to the lowest shard possible
95                 throw new UnsupportedOperationException("Listener should be registered directly into initialDataChangeEvent subshard");
96             }
97         }
98
99         initialDataChangeEvent(listenerPath, listener);
100
101         return reg;
102     }
103
104     private <L extends DOMDataTreeChangeListener> void initialDataChangeEvent(final YangInstanceIdentifier listenerPath, final L listener) {
105         // FIXME Add support for wildcard listeners
106         final Optional<NormalizedNode<?, ?>> preExistingData = dataTree.takeSnapshot()
107                 .readNode(YangInstanceIdentifier.create(stripShardPath(listenerPath)));
108         final DataTreeCandidate initialCandidate;
109
110         if (preExistingData.isPresent()) {
111             final NormalizedNode<?, ?> data = preExistingData.get();
112             Preconditions.checkState(data instanceof DataContainerNode,
113                     "Expected DataContainer node, but was {}", data.getClass());
114             // if we are listening on root of some shard we still get
115             // empty normalized node, root is always present
116             if (((DataContainerNode) data).getValue().isEmpty()) {
117                 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
118                         new EmptyDataTreeCandidateNode(data.getIdentifier()));
119             } else {
120                 initialCandidate = DataTreeCandidates.fromNormalizedNode(listenerPath,
121                         translateRootShardIdentifierToListenerPath(listenerPath, preExistingData.get()));
122             }
123         } else {
124             initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
125                     new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument()));
126         }
127
128         listener.onDataTreeChanged(Collections.singleton(initialCandidate));
129     }
130
131     private NormalizedNode<?, ?> translateRootShardIdentifierToListenerPath(final YangInstanceIdentifier listenerPath,
132                                                                             final NormalizedNode<?, ?> node) {
133         if (listenerPath.isEmpty()) {
134             return node;
135         }
136
137         final NormalizedNodeBuilder nodeBuilder;
138         if (node instanceof ContainerNode) {
139             nodeBuilder = ImmutableContainerNodeBuilder.create().withValue(((ContainerNode) node).getValue());
140         } else if (node instanceof MapEntryNode) {
141             nodeBuilder = ImmutableMapEntryNodeBuilder.create().withValue(((MapEntryNode) node).getValue());
142         } else {
143             throw new IllegalArgumentException("Expected ContainerNode or MapEntryNode, but was " + node.getClass());
144         }
145         nodeBuilder.withNodeIdentifier(listenerPath.getLastPathArgument());
146         return nodeBuilder.build();
147     }
148
149     private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath, final DOMDataTreeListenerWithSubshards listener) {
150         LOG.debug("Registering root listener at {}", listenerPath);
151         final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(listenerPath.getPathArguments());
152         final AbstractDOMDataTreeChangeListenerRegistration<L> registration = new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
153             @Override
154             protected void removeRegistration() {
155                 listener.close();
156                 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
157                 registrationRemoved(this);
158             }
159         };
160         addRegistration(node, registration);
161         return registration;
162     }
163
164     private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
165         if (shardPath.isEmpty()) {
166             return listenerPath.getPathArguments();
167         }
168
169         final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
170         final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
171         final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
172
173         while (shardIter.hasNext()) {
174             if (shardIter.next().equals(listenerIter.next())) {
175                 listenerIter.remove();
176             } else {
177                 break;
178             }
179         }
180
181         return listenerPathArgs;
182     }
183
184     private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
185
186         private final DataTree dataTree;
187         private final YangInstanceIdentifier listenerPath;
188         private final DOMDataTreeChangeListener delegate;
189
190         private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
191                 new HashMap<>();
192
193         DOMDataTreeListenerWithSubshards(final DataTree dataTree,
194                                          final YangInstanceIdentifier listenerPath,
195                                          final DOMDataTreeChangeListener delegate) {
196             this.dataTree = Preconditions.checkNotNull(dataTree);
197             this.listenerPath = Preconditions.checkNotNull(listenerPath);
198             this.delegate = Preconditions.checkNotNull(delegate);
199         }
200
201         @Override
202         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
203             LOG.debug("Received data changed {}", changes.iterator().next());
204             delegate.onDataTreeChanged(changes);
205         }
206
207         void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
208             final List<DataTreeCandidate> newCandidates = changes.stream()
209                     .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
210                     .collect(Collectors.toList());
211             delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
212         }
213
214         void addSubshard(final ChildShardContext context) {
215             Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
216                     "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
217
218             final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
219             // since this is going into subshard we want to listen for ALL changes in the subshard
220             registrations.put(context.getPrefix().getRootIdentifier(),
221                     listenableShard.registerTreeChangeListener(context.getPrefix().getRootIdentifier(),
222                             changes -> onDataTreeChanged(context.getPrefix().getRootIdentifier(), changes)));
223         }
224
225         void close() {
226             for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
227                 registration.close();
228             }
229             registrations.clear();
230         }
231
232         private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
233             final DataTreeModification modification = dataTree.takeSnapshot().newModification();
234             for (final DataTreeCandidate change : changes) {
235                 DataTreeCandidates.applyToModification(modification, change);
236             }
237
238             modification.ready();
239             try {
240                 dataTree.validate(modification);
241             } catch (final DataValidationFailedException e) {
242                 LOG.error("Validation failed for built modification", e);
243                 throw new RuntimeException("Notification validation failed", e);
244             }
245
246             // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
247             DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
248             for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
249                 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
250             }
251
252             if (modifiedChild == null) {
253                 modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
254             }
255
256             return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
257         }
258     }
259
260     private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
261
262         private final PathArgument identifier;
263
264         EmptyDataTreeCandidateNode(final PathArgument identifier) {
265             Preconditions.checkNotNull(identifier, "Identifier should not be null");
266             this.identifier = identifier;
267         }
268
269         @Nonnull
270         @Override
271         public PathArgument getIdentifier() {
272             return identifier;
273         }
274
275         @Nonnull
276         @Override
277         public Collection<DataTreeCandidateNode> getChildNodes() {
278             return Collections.<DataTreeCandidateNode>emptySet();
279         }
280
281         @Nullable
282         @Override
283         public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
284             return null;
285         }
286
287         @Nonnull
288         @Override
289         public ModificationType getModificationType() {
290             return ModificationType.UNMODIFIED;
291         }
292
293         @Nonnull
294         @Override
295         public Optional<NormalizedNode<?, ?>> getDataAfter() {
296             return Optional.absent();
297         }
298
299         @Nonnull
300         @Override
301         public Optional<NormalizedNode<?, ?>> getDataBefore() {
302             return Optional.absent();
303         }
304     }
305
306 }