BUG-3128: Extract abstract shard implementations.
[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.collect.Iterables;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.Set;
17 import javax.annotation.concurrent.GuardedBy;
18 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
19 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
20 import org.opendaylight.mdsal.dom.api.DOMDataTreeLoopException;
21 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
22 import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingConflictException;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingService;
26 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTable;
27 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTableEntry;
28 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
29 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
30 import org.opendaylight.yangtools.concepts.ListenerRegistration;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTreeShardingService {
35     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTree.class);
36
37     @GuardedBy("this")
38     private final DOMDataTreePrefixTable<DOMDataTreeShardRegistration<?>> shards = DOMDataTreePrefixTable.create();
39     @GuardedBy("this")
40     private final DOMDataTreePrefixTable<DOMDataTreeProducer> producers = DOMDataTreePrefixTable.create();
41
42     void removeShard(final DOMDataTreeShardRegistration<?> reg) {
43         final DOMDataTreeIdentifier prefix = reg.getPrefix();
44         final DOMDataTreeShardRegistration<?> parentReg;
45
46         synchronized (this) {
47             shards.remove(prefix);
48             parentReg = shards.lookup(prefix).getValue();
49
50             /*
51              * FIXME: adjust all producers and listeners. This is tricky, as we need different
52              * locking strategy, simply because we risk AB/BA deadlock with a producer being split
53              * off from a producer.
54              */
55         }
56
57         if (parentReg != null) {
58             parentReg.getInstance().onChildDetached(prefix, reg.getInstance());
59         }
60     }
61
62     @Override
63     public <T extends DOMDataTreeShard> ListenerRegistration<T> registerDataTreeShard(
64             final DOMDataTreeIdentifier prefix, final T shard, final DOMDataTreeProducer producer)
65                     throws DOMDataTreeShardingConflictException {
66
67         final DOMDataTreeIdentifier firstSubtree = Iterables.getOnlyElement(((
68                 ShardedDOMDataTreeProducer) producer).getSubtrees());
69         Preconditions.checkArgument(firstSubtree != null, "Producer that is used to verify namespace claim can"
70                 + " only claim a single namespace");
71         Preconditions.checkArgument(prefix.equals(firstSubtree), "Trying to register shard to a different namespace"
72                 + " than the producer has claimed");
73
74         final DOMDataTreeShardRegistration<T> reg;
75         final DOMDataTreeShardRegistration<?> parentReg;
76
77         synchronized (this) {
78             /*
79              * Lookup the parent shard (e.g. the one which currently matches the prefix),
80              * and if it exists, check if its registration prefix does not collide with
81              * this registration.
82              */
83             final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> parent = shards.lookup(prefix);
84             if (parent != null) {
85                 parentReg = parent.getValue();
86                 if (parentReg != null && prefix.equals(parentReg.getPrefix())) {
87                     throw new DOMDataTreeShardingConflictException(String.format(
88                             "Prefix %s is already occupied by shard %s", prefix, parentReg.getInstance()));
89                 }
90             } else {
91                 parentReg = null;
92             }
93
94             // FIXME: wrap the shard in a proper adaptor based on implemented interface
95
96             reg = new DOMDataTreeShardRegistration<>(this, prefix, shard);
97
98             shards.store(prefix, reg);
99
100             ((ShardedDOMDataTreeProducer) producer).subshardAdded(Collections.singletonMap(prefix, shard));
101         }
102
103         // Notify the parent shard
104         if (parentReg != null) {
105             parentReg.getInstance().onChildAttached(prefix, shard);
106         }
107
108         return reg;
109     }
110
111     @GuardedBy("this")
112     private DOMDataTreeProducer findProducer(final DOMDataTreeIdentifier subtree) {
113
114         final DOMDataTreePrefixTableEntry<DOMDataTreeProducer> producerEntry = producers.lookup(subtree);
115         if (producerEntry != null) {
116             return producerEntry.getValue();
117         }
118         return null;
119     }
120
121     synchronized void destroyProducer(final ShardedDOMDataTreeProducer producer) {
122         for (final DOMDataTreeIdentifier s : producer.getSubtrees()) {
123             producers.remove(s);
124         }
125     }
126
127     @GuardedBy("this")
128     private DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees,
129             final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
130         // Record the producer's attachment points
131         final DOMDataTreeProducer ret = ShardedDOMDataTreeProducer.create(this, subtrees, shardMap);
132         for (final DOMDataTreeIdentifier subtree : subtrees) {
133             producers.store(subtree, ret);
134         }
135
136         return ret;
137     }
138
139     @Override
140     public synchronized DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
141         Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
142
143         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
144         for (final DOMDataTreeIdentifier subtree : subtrees) {
145             // Attempting to create a disconnected producer -- all subtrees have to be unclaimed
146             final DOMDataTreeProducer producer = findProducer(subtree);
147             Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", subtree, producer);
148
149             final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> possibleShardReg =
150                     shards.lookup(subtree);
151             if (possibleShardReg != null && possibleShardReg.getValue() != null) {
152                 shardMap.put(subtree, possibleShardReg.getValue().getInstance());
153             }
154         }
155
156         return createProducer(subtrees, shardMap);
157     }
158
159     synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent,
160             final Collection<DOMDataTreeIdentifier> subtrees) {
161         Preconditions.checkNotNull(parent);
162
163         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
164         for (final DOMDataTreeIdentifier s : subtrees) {
165             shardMap.put(s, shards.lookup(s).getValue().getInstance());
166         }
167
168         return createProducer(subtrees, shardMap);
169     }
170
171     @SuppressWarnings("checkstyle:IllegalCatch")
172     @Override
173     public synchronized <T extends DOMDataTreeListener> ListenerRegistration<T> registerListener(final T listener,
174             final Collection<DOMDataTreeIdentifier> subtrees, final boolean allowRxMerges,
175             final Collection<DOMDataTreeProducer> producers) throws DOMDataTreeLoopException {
176         Preconditions.checkNotNull(listener, "listener");
177         Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees must not be empty.");
178         final ShardedDOMDataTreeListenerContext<T> listenerContext =
179                 ShardedDOMDataTreeListenerContext.create(listener, subtrees, allowRxMerges);
180         try {
181             // FIXME: Add attachment of producers
182             for (final DOMDataTreeProducer producer : producers) {
183                 Preconditions.checkArgument(producer instanceof ShardedDOMDataTreeProducer);
184                 final ShardedDOMDataTreeProducer castedProducer = ((ShardedDOMDataTreeProducer) producer);
185                 simpleLoopCheck(subtrees, castedProducer.getSubtrees());
186                 // FIXME: We should also unbound listeners
187                 castedProducer.bindToListener(listenerContext);
188             }
189
190             for (final DOMDataTreeIdentifier subtree : subtrees) {
191                 final DOMDataTreeShard shard = shards.lookup(subtree).getValue().getInstance();
192                 // FIXME: What should we do if listener is wildcard? And shards are on per
193                 // node basis?
194                 Preconditions.checkArgument(shard instanceof DOMStoreTreeChangePublisher,
195                         "Subtree %s does not point to listenable subtree.", subtree);
196
197                 listenerContext.register(subtree, (DOMStoreTreeChangePublisher) shard);
198             }
199         } catch (final Exception e) {
200             listenerContext.close();
201             throw e;
202         }
203         return new AbstractListenerRegistration<T>(listener) {
204             @Override
205             protected void removeRegistration() {
206                 ShardedDOMDataTree.this.removeListener(listenerContext);
207             }
208         };
209     }
210
211     private static void simpleLoopCheck(final Collection<DOMDataTreeIdentifier> listen,
212             final Set<DOMDataTreeIdentifier> writes) throws DOMDataTreeLoopException {
213         for (final DOMDataTreeIdentifier listenPath : listen) {
214             for (final DOMDataTreeIdentifier writePath : writes) {
215                 if (listenPath.contains(writePath)) {
216                     throw new DOMDataTreeLoopException(String.format(
217                             "Listener must not listen on parent (%s), and also writes child (%s)", listenPath,
218                             writePath));
219                 } else if (writePath.contains(listenPath)) {
220                     throw new DOMDataTreeLoopException(
221                             String.format("Listener must not write parent (%s), and also listen on child (%s)",
222                                     writePath, listenPath));
223                 }
224             }
225         }
226     }
227
228     void removeListener(final ShardedDOMDataTreeListenerContext<?> listener) {
229         // FIXME: detach producers
230         listener.close();
231     }
232 }