2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.dom.broker;
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;
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;
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;
46 public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTreeShardingService {
47 private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTree.class);
50 private final DOMDataTreePrefixTable<DOMDataTreeShardRegistration<?>> shards = DOMDataTreePrefixTable.create();
52 private final DOMDataTreePrefixTable<DOMDataTreeProducer> producers = DOMDataTreePrefixTable.create();
54 private final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> producerMap = new HashMap<>();
56 void removeShard(final DOMDataTreeShardRegistration<?> reg) {
57 final DOMDataTreeIdentifier prefix = reg.getPrefix();
58 DOMDataTreeShard lookupShard = null;
60 shards.remove(prefix);
61 final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> parentRegEntry = shards.lookup(prefix);
62 if (parentRegEntry != null) {
63 lookupShard = parentRegEntry.getValue().getInstance();
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.
72 if (lookupShard != null) {
73 lookupShard.onChildDetached(prefix, reg.getInstance());
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;
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");
90 final DOMDataTreeShardRegistration<T> reg;
91 final DOMDataTreeShardRegistration<?> parentReg;
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
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()));
110 // FIXME: wrap the shard in a proper adaptor based on implemented interface
112 reg = new DOMDataTreeShardRegistration<>(this, prefix, shard);
114 shards.store(prefix, reg);
116 prod.subshardAdded(Collections.singletonMap(prefix, shard));
119 // Notify the parent shard
120 if (parentReg != null) {
121 parentReg.getInstance().onChildAttached(prefix, shard);
128 private DOMDataTreeProducer findProducer(final DOMDataTreeIdentifier subtree) {
130 final DOMDataTreePrefixTableEntry<DOMDataTreeProducer> producerEntry = producers.lookup(subtree);
131 if (producerEntry != null) {
132 return producerEntry.getValue();
137 synchronized void destroyProducer(final ShardedDOMDataTreeProducer producer) {
138 for (final DOMDataTreeIdentifier s : producer.getSubtrees()) {
140 producerMap.remove(s);
145 public ClassToInstanceMap<DOMDataTreeServiceExtension> getExtensions() {
146 return ImmutableClassToInstanceMap.of();
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);
163 public synchronized DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
164 checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
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);
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());
178 final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> possibleShardReg =
179 shards.lookup(subtree);
180 if (possibleShardReg != null && possibleShardReg.getValue() != null) {
181 shardMap.put(subtree, possibleShardReg.getValue().getInstance());
185 return createProducer(subtrees, shardMap);
188 synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent,
189 final Collection<DOMDataTreeIdentifier> subtrees) {
190 requireNonNull(parent);
192 final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
193 for (final DOMDataTreeIdentifier s : subtrees) {
194 shardMap.put(s, shards.lookup(s).getValue().getInstance());
197 return createProducer(subtrees, shardMap);
200 @SuppressWarnings({ "checkstyle:IllegalCatch", "checkstyle:hiddenField" })
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");
207 // Cross-check specified trees for exclusivity and eliminate duplicates, noDupSubtrees is effectively a Set
208 final Collection<DOMDataTreeIdentifier> noDupSubtrees;
209 switch (subtrees.size()) {
211 throw new IllegalArgumentException("Subtrees must not be empty.");
213 noDupSubtrees = subtrees;
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,
226 noDupSubtrees = ImmutableSet.copyOf(subtrees);
229 LOG.trace("Requested registration of listener {} to subtrees {}", listener, noDupSubtrees);
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);
239 LOG.trace("Listener {} is attaching to shards {}", listener, needed);
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.",
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());
255 final ListenerRegistration<?> underlyingRegistration = createRegisteredListener(listener, needed.asMap(),
256 allowRxMerges, producers);
257 return new AbstractListenerRegistration<T>(listener) {
259 protected void removeRegistration() {
260 ShardedDOMDataTree.this.removeListener(listener);
261 underlyingRegistration.close();
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);
275 return DOMDataTreeListenerAggregator.aggregateIfNeeded(userListener, needed, allowRxMerges,
276 DOMDataTreeShardRegistration::getInstance);
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,
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));
296 void removeListener(final DOMDataTreeListener listener) {
297 // FIXME: detach producers