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