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