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