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