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