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