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