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