086689f2c1e0e14e39eb91def9d2c08d384cff51
[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.Preconditions;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.stream.Collectors;
20 import javax.annotation.Nonnull;
21 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
22 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
24 import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
25 import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
26 import org.opendaylight.mdsal.dom.spi.store.AbstractDOMStoreTreeChangePublisher;
27 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
28 import org.opendaylight.yangtools.concepts.ListenerRegistration;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
35 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher implements DOMStoreTreeChangePublisher {
41
42     private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
43
44     private YangInstanceIdentifier shardPath;
45     private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
46     private final DataTree dataTree;
47
48     protected AbstractDOMShardTreeChangePublisher(final DataTree dataTree,
49                                                   final YangInstanceIdentifier shardPath,
50                                                   final Map<DOMDataTreeIdentifier, ChildShardContext> childShards) {
51         this.dataTree = Preconditions.checkNotNull(dataTree);
52         this.shardPath = Preconditions.checkNotNull(shardPath);
53         this.childShards = Preconditions.checkNotNull(childShards);
54     }
55
56     @Override
57     public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
58         takeLock();
59         try {
60             return setupListenerContext(path, listener);
61         } finally {
62             releaseLock();
63         }
64     }
65
66     private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
67         // we need to register the listener registration path based on the shards root
68         // we have to strip the shard path from the listener path and then register
69         YangInstanceIdentifier strippedIdentifier =  listenerPath;
70         if (!shardPath.isEmpty()) {
71             strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
72         }
73
74         final DOMDataTreeListenerWithSubshards subshardListener = new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
75         final AbstractDOMDataTreeChangeListenerRegistration<L> reg = setupContextWithoutSubshards(strippedIdentifier, subshardListener);
76
77         for (final ChildShardContext maybeAffected : childShards.values()) {
78             if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
79                 // consumer has a subshard somewhere on lower level
80                 // register to the notification manager with snapshot and forward child notifications to parent
81                 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
82                 subshardListener.addSubshard(maybeAffected);
83             } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
84                 // bind path is inside subshard
85                 // TODO can this happen? seems like in ShardedDOMDataTree we are already registering to the lowest shard possible
86                 throw new UnsupportedOperationException("Listener should be registered directly into a subshard");
87             }
88         }
89         return reg;
90     }
91
92     private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath, final DOMDataTreeListenerWithSubshards listener) {
93         LOG.debug("Registering root listener at {}", listenerPath);
94         final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(listenerPath.getPathArguments());
95         final AbstractDOMDataTreeChangeListenerRegistration<L> registration = new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
96             @Override
97             protected void removeRegistration() {
98                 listener.close();
99                 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
100                 registrationRemoved(this);
101             }
102         };
103         addRegistration(node, registration);
104         return registration;
105     }
106
107     private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
108         if (shardPath.isEmpty()) {
109             return listenerPath.getPathArguments();
110         }
111
112         final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
113         final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
114         final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
115
116         while (shardIter.hasNext()) {
117             if (shardIter.next().equals(listenerIter.next())) {
118                 listenerIter.remove();
119             } else {
120                 break;
121             }
122         }
123
124         return listenerPathArgs;
125     }
126
127     private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
128
129         private final DataTree dataTree;
130         private final YangInstanceIdentifier listenerPath;
131         private final DOMDataTreeChangeListener delegate;
132
133         private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
134                 new HashMap<>();
135
136         DOMDataTreeListenerWithSubshards(final DataTree dataTree,
137                                          final YangInstanceIdentifier listenerPath,
138                                          final DOMDataTreeChangeListener delegate) {
139             this.dataTree = Preconditions.checkNotNull(dataTree);
140             this.listenerPath = Preconditions.checkNotNull(listenerPath);
141             this.delegate = Preconditions.checkNotNull(delegate);
142         }
143
144         @Override
145         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
146             LOG.debug("Received data changed {}", changes.iterator().next());
147             delegate.onDataTreeChanged(changes);
148         }
149
150         void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
151             final List<DataTreeCandidate> newCandidates = changes.stream()
152                     .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
153                     .collect(Collectors.toList());
154             delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
155         }
156
157         void addSubshard(final ChildShardContext context) {
158             Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
159                     "All subshards that are a part of ListenerContext need to be listenable");
160
161             final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
162             // since this is going into subshard we want to listen for ALL changes in the subshard
163             registrations.put(context.getPrefix().getRootIdentifier(),
164                     listenableShard.registerTreeChangeListener(context.getPrefix().getRootIdentifier(),
165                             changes -> onDataTreeChanged(context.getPrefix().getRootIdentifier(), changes)));
166         }
167
168         void close() {
169             for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
170                 registration.close();
171             }
172             registrations.clear();
173         }
174
175         private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
176             final DataTreeModification modification = dataTree.takeSnapshot().newModification();
177             for (final DataTreeCandidate change : changes) {
178                 DataTreeCandidates.applyToModification(modification, change);
179             }
180
181             modification.ready();
182             try {
183                 dataTree.validate(modification);
184             } catch (DataValidationFailedException e) {
185                 LOG.error("Validation failed for built modification", e);
186                 throw new RuntimeException("Notification validation failed", e);
187             }
188
189             // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
190             DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
191             for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
192                 // there should be no null pointers since we wouldn't get a notification change
193                 // if there was no node modified at the listener's location
194                 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
195             }
196
197             return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
198         }
199     }
200 }