Fix warnings in AbstractDOMShardTreeChangePublisher
[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 static NormalizedNode<?, ?> translateRootShardIdentifierToListenerPath(
140             final YangInstanceIdentifier listenerPath, 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         @SuppressWarnings("unchecked")
164         final AbstractDOMDataTreeChangeListenerRegistration<L> registration =
165                 new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
166             @Override
167             protected void removeRegistration() {
168                 listener.close();
169                 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
170                 registrationRemoved(this);
171             }
172         };
173         addRegistration(node, registration);
174         return registration;
175     }
176
177     private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
178         if (shardPath.isEmpty()) {
179             return listenerPath.getPathArguments();
180         }
181
182         final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
183         final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
184         final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
185
186         while (shardIter.hasNext()) {
187             if (shardIter.next().equals(listenerIter.next())) {
188                 listenerIter.remove();
189             } else {
190                 break;
191             }
192         }
193
194         return listenerPathArgs;
195     }
196
197     private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
198
199         private final DataTree dataTree;
200         private final YangInstanceIdentifier listenerPath;
201         private final DOMDataTreeChangeListener delegate;
202
203         private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
204                 new HashMap<>();
205
206         DOMDataTreeListenerWithSubshards(final DataTree dataTree,
207                                          final YangInstanceIdentifier listenerPath,
208                                          final DOMDataTreeChangeListener delegate) {
209             this.dataTree = Preconditions.checkNotNull(dataTree);
210             this.listenerPath = Preconditions.checkNotNull(listenerPath);
211             this.delegate = Preconditions.checkNotNull(delegate);
212         }
213
214         @Override
215         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
216             LOG.debug("Received data changed {}", changes);
217             delegate.onDataTreeChanged(changes);
218         }
219
220         void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
221             final List<DataTreeCandidate> newCandidates = changes.stream()
222                     .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
223                     .collect(Collectors.toList());
224             delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
225         }
226
227         void addSubshard(final ChildShardContext context) {
228             Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
229                     "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
230
231             final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
232             // since this is going into subshard we want to listen for ALL changes in the subshard
233             registrations.put(context.getPrefix().getRootIdentifier(),
234                 listenableShard.registerTreeChangeListener(
235                         context.getPrefix().getRootIdentifier(), changes -> onDataTreeChanged(
236                                 context.getPrefix().getRootIdentifier(), changes)));
237         }
238
239         void close() {
240             for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
241                 registration.close();
242             }
243             registrations.clear();
244         }
245
246         private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
247             final DataTreeModification modification = dataTree.takeSnapshot().newModification();
248             for (final DataTreeCandidate change : changes) {
249                 DataTreeCandidates.applyToModification(modification, change);
250             }
251
252             modification.ready();
253             try {
254                 dataTree.validate(modification);
255             } catch (final DataValidationFailedException e) {
256                 LOG.error("Validation failed for built modification", e);
257                 throw new RuntimeException("Notification validation failed", e);
258             }
259
260             // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
261             DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
262             for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
263                 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
264             }
265
266             if (modifiedChild == null) {
267                 modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
268             }
269
270             return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
271         }
272     }
273
274     private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
275
276         private final PathArgument identifier;
277
278         EmptyDataTreeCandidateNode(final PathArgument identifier) {
279             this.identifier = Preconditions.checkNotNull(identifier, "Identifier should not be null");
280         }
281
282         @Nonnull
283         @Override
284         public PathArgument getIdentifier() {
285             return identifier;
286         }
287
288         @Nonnull
289         @Override
290         public Collection<DataTreeCandidateNode> getChildNodes() {
291             return Collections.emptySet();
292         }
293
294         @Nullable
295         @Override
296         public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
297             return null;
298         }
299
300         @Nonnull
301         @Override
302         public ModificationType getModificationType() {
303             return ModificationType.UNMODIFIED;
304         }
305
306         @Nonnull
307         @Override
308         public Optional<NormalizedNode<?, ?>> getDataAfter() {
309             return Optional.absent();
310         }
311
312         @Nonnull
313         @Override
314         public Optional<NormalizedNode<?, ?>> getDataBefore() {
315             return Optional.absent();
316         }
317     }
318
319 }