Do not generate 'isFoo()' methods
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / ShardedDOMDataTree.java
1 /*
2  * Copyright (c) 2015 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.broker;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.collect.ArrayListMultimap;
15 import com.google.common.collect.ClassToInstanceMap;
16 import com.google.common.collect.ImmutableClassToInstanceMap;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.Iterables;
19 import com.google.common.collect.ListMultimap;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.Set;
25 import org.checkerframework.checker.lock.qual.GuardedBy;
26 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeLoopException;
29 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
30 import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
31 import org.opendaylight.mdsal.dom.api.DOMDataTreeServiceExtension;
32 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingConflictException;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingService;
35 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTable;
36 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTableEntry;
37 import org.opendaylight.mdsal.dom.spi.shard.DOMDataTreeListenerAggregator;
38 import org.opendaylight.mdsal.dom.spi.shard.ListenableDOMDataTreeShard;
39 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
40 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
41 import org.opendaylight.yangtools.concepts.ListenerRegistration;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46 public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTreeShardingService {
47     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTree.class);
48
49     @GuardedBy("this")
50     private final DOMDataTreePrefixTable<DOMDataTreeShardRegistration<?>> shards = DOMDataTreePrefixTable.create();
51     @GuardedBy("this")
52     private final DOMDataTreePrefixTable<DOMDataTreeProducer> producers = DOMDataTreePrefixTable.create();
53     @GuardedBy("this")
54     private final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> producerMap = new HashMap<>();
55
56     void removeShard(final DOMDataTreeShardRegistration<?> reg) {
57         final DOMDataTreeIdentifier prefix = reg.getPrefix();
58         DOMDataTreeShard lookupShard = null;
59         synchronized (this) {
60             shards.remove(prefix);
61             final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> parentRegEntry = shards.lookup(prefix);
62             if (parentRegEntry != null) {
63                 lookupShard = parentRegEntry.getValue().getInstance();
64             }
65             /*
66              * FIXME: adjust all producers and listeners. This is tricky, as we need different
67              * locking strategy, simply because we risk AB/BA deadlock with a producer being split
68              * off from a producer.
69              */
70         }
71
72         if (lookupShard != null) {
73             lookupShard.onChildDetached(prefix, reg.getInstance());
74         }
75     }
76
77     @Override
78     public <T extends DOMDataTreeShard> DOMDataTreeShardRegistration<T> registerDataTreeShard(
79             final DOMDataTreeIdentifier prefix, final T shard, final DOMDataTreeProducer producer)
80                     throws DOMDataTreeShardingConflictException {
81         checkArgument(producer instanceof ShardedDOMDataTreeProducer, "Unsupported producer %s", producer);
82         final ShardedDOMDataTreeProducer prod = (ShardedDOMDataTreeProducer) producer;
83
84         final DOMDataTreeIdentifier firstSubtree = Iterables.getOnlyElement(prod.getSubtrees());
85         checkArgument(firstSubtree != null, "Producer that is used to verify namespace claim can"
86                 + " only claim a single namespace");
87         checkArgument(prefix.equals(firstSubtree), "Trying to register shard to a different namespace"
88                 + " than the producer has claimed");
89
90         final DOMDataTreeShardRegistration<T> reg;
91         final DOMDataTreeShardRegistration<?> parentReg;
92
93         synchronized (this) {
94             /*
95              * Lookup the parent shard (e.g. the one which currently matches the prefix),
96              * and if it exists, check if its registration prefix does not collide with
97              * this registration.
98              */
99             final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> parent = shards.lookup(prefix);
100             if (parent != null) {
101                 parentReg = parent.getValue();
102                 if (parentReg != null && prefix.equals(parentReg.getPrefix())) {
103                     throw new DOMDataTreeShardingConflictException(String.format(
104                             "Prefix %s is already occupied by shard %s", prefix, parentReg.getInstance()));
105                 }
106             } else {
107                 parentReg = null;
108             }
109
110             // FIXME: wrap the shard in a proper adaptor based on implemented interface
111
112             reg = new DOMDataTreeShardRegistration<>(this, prefix, shard);
113
114             shards.store(prefix, reg);
115
116             prod.subshardAdded(Collections.singletonMap(prefix, shard));
117         }
118
119         // Notify the parent shard
120         if (parentReg != null) {
121             parentReg.getInstance().onChildAttached(prefix, shard);
122         }
123
124         return reg;
125     }
126
127     @GuardedBy("this")
128     private DOMDataTreeProducer findProducer(final DOMDataTreeIdentifier subtree) {
129
130         final DOMDataTreePrefixTableEntry<DOMDataTreeProducer> producerEntry = producers.lookup(subtree);
131         if (producerEntry != null) {
132             return producerEntry.getValue();
133         }
134         return null;
135     }
136
137     synchronized void destroyProducer(final ShardedDOMDataTreeProducer producer) {
138         for (final DOMDataTreeIdentifier s : producer.getSubtrees()) {
139             producers.remove(s);
140             producerMap.remove(s);
141         }
142     }
143
144     @Override
145     public ClassToInstanceMap<DOMDataTreeServiceExtension> getExtensions() {
146         return ImmutableClassToInstanceMap.of();
147     }
148
149     @GuardedBy("this")
150     private DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees,
151             final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
152         // Record the producer's attachment points
153         final DOMDataTreeProducer ret = ShardedDOMDataTreeProducer.create(this, subtrees, shardMap);
154         for (final DOMDataTreeIdentifier subtree : subtrees) {
155             producers.store(subtree, ret);
156             producerMap.put(subtree, ret);
157         }
158
159         return ret;
160     }
161
162     @Override
163     public synchronized DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
164         checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
165
166         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
167         for (final DOMDataTreeIdentifier subtree : subtrees) {
168             // Attempting to create a disconnected producer -- all subtrees have to be unclaimed
169             final DOMDataTreeProducer producer = findProducer(subtree);
170             checkArgument(producer == null, "Subtree %s is attached to producer %s", subtree, producer);
171
172             for (Map.Entry<DOMDataTreeIdentifier, DOMDataTreeProducer> producerEntry : producerMap.entrySet()) {
173                 checkArgument(!subtree.contains(producerEntry.getKey()),
174                     "Subtree %s contains subtree %s which is already attached to producer %s.",
175                     subtree, producerEntry.getKey(), producerEntry.getValue());
176             }
177
178             final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> possibleShardReg =
179                     shards.lookup(subtree);
180             if (possibleShardReg != null && possibleShardReg.getValue() != null) {
181                 shardMap.put(subtree, possibleShardReg.getValue().getInstance());
182             }
183         }
184
185         return createProducer(subtrees, shardMap);
186     }
187
188     synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent,
189             final Collection<DOMDataTreeIdentifier> subtrees) {
190         requireNonNull(parent);
191
192         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
193         for (final DOMDataTreeIdentifier s : subtrees) {
194             shardMap.put(s, shards.lookup(s).getValue().getInstance());
195         }
196
197         return createProducer(subtrees, shardMap);
198     }
199
200     @SuppressWarnings({ "checkstyle:IllegalCatch", "checkstyle:hiddenField" })
201     @Override
202     public synchronized <T extends DOMDataTreeListener> ListenerRegistration<T> registerListener(final T listener,
203             final Collection<DOMDataTreeIdentifier> subtrees, final boolean allowRxMerges,
204             final Collection<DOMDataTreeProducer> producers) throws DOMDataTreeLoopException {
205         requireNonNull(listener, "listener");
206
207         // Cross-check specified trees for exclusivity and eliminate duplicates, noDupSubtrees is effectively a Set
208         final Collection<DOMDataTreeIdentifier> noDupSubtrees;
209         switch (subtrees.size()) {
210             case 0:
211                 throw new IllegalArgumentException("Subtrees must not be empty.");
212             case 1:
213                 noDupSubtrees = subtrees;
214                 break;
215             default:
216                 // Check subtrees for mutual inclusion, this is an O(N**2) operation
217                 for (DOMDataTreeIdentifier toCheck : subtrees) {
218                     for (DOMDataTreeIdentifier against : subtrees) {
219                         if (!toCheck.equals(against)) {
220                             checkArgument(!toCheck.contains(against), "Subtree %s contains subtree %s", toCheck,
221                                 against);
222                         }
223                     }
224                 }
225
226                 noDupSubtrees = ImmutableSet.copyOf(subtrees);
227         }
228
229         LOG.trace("Requested registration of listener {} to subtrees {}", listener, noDupSubtrees);
230
231         // Lookup shards corresponding to subtrees and construct a map of which subtrees we want from which shard
232         final ListMultimap<DOMDataTreeShardRegistration<?>, DOMDataTreeIdentifier> needed =
233                 ArrayListMultimap.create();
234         for (final DOMDataTreeIdentifier subtree : subtrees) {
235             final DOMDataTreeShardRegistration<?> reg = verifyNotNull(shards.lookup(subtree).getValue());
236             needed.put(reg, subtree);
237         }
238
239         LOG.trace("Listener {} is attaching to shards {}", listener, needed);
240
241         // Sanity check: all selected shards have to support one of the listening interfaces
242         needed.asMap().forEach((reg, trees) -> {
243             final DOMDataTreeShard shard = reg.getInstance();
244             checkArgument(shard instanceof ListenableDOMDataTreeShard
245                 || shard instanceof DOMStoreTreeChangePublisher, "Subtrees %s do not point to listenable subtree.",
246                 trees);
247         });
248
249         // Sanity check: all producers have to come from this implementation and must not form loops
250         for (DOMDataTreeProducer producer : producers) {
251             checkArgument(producer instanceof ShardedDOMDataTreeProducer);
252             simpleLoopCheck(subtrees, ((ShardedDOMDataTreeProducer) producer).getSubtrees());
253         }
254
255         final ListenerRegistration<?> underlyingRegistration = createRegisteredListener(listener, needed.asMap(),
256             allowRxMerges, producers);
257         return new AbstractListenerRegistration<T>(listener) {
258             @Override
259             protected void removeRegistration() {
260                 ShardedDOMDataTree.this.removeListener(listener);
261                 underlyingRegistration.close();
262             }
263         };
264     }
265
266     private static ListenerRegistration<?> createRegisteredListener(final DOMDataTreeListener userListener,
267             final Map<DOMDataTreeShardRegistration<?>, Collection<DOMDataTreeIdentifier>> needed,
268             final boolean allowRxMerges, final Collection<DOMDataTreeProducer> producers) {
269         // FIXME: Add attachment of producers
270         for (final DOMDataTreeProducer producer : producers) {
271             // FIXME: We should also unbound listeners
272             ((ShardedDOMDataTreeProducer) producer).bindToListener(userListener);
273         }
274
275         return DOMDataTreeListenerAggregator.aggregateIfNeeded(userListener, needed, allowRxMerges,
276             DOMDataTreeShardRegistration::getInstance);
277     }
278
279     private static void simpleLoopCheck(final Collection<DOMDataTreeIdentifier> listen,
280             final Set<DOMDataTreeIdentifier> writes) throws DOMDataTreeLoopException {
281         for (final DOMDataTreeIdentifier listenPath : listen) {
282             for (final DOMDataTreeIdentifier writePath : writes) {
283                 if (listenPath.contains(writePath)) {
284                     throw new DOMDataTreeLoopException(String.format(
285                             "Listener must not listen on parent (%s), and also writes child (%s)", listenPath,
286                             writePath));
287                 } else if (writePath.contains(listenPath)) {
288                     throw new DOMDataTreeLoopException(
289                             String.format("Listener must not write parent (%s), and also listen on child (%s)",
290                                     writePath, listenPath));
291                 }
292             }
293         }
294     }
295
296     void removeListener(final DOMDataTreeListener listener) {
297         // FIXME: detach producers
298     }
299 }