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