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