Bug 3868: Added support for DOMDataTreeListener
[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.TreeMap;
17 import javax.annotation.concurrent.GuardedBy;
18 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
19 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
20 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
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.store.DOMStoreTreeChangePublisher;
27 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
28 import org.opendaylight.yangtools.concepts.ListenerRegistration;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTreeShardingService {
33     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTree.class);
34     private final Map<LogicalDatastoreType, ShardingTableEntry> shardingTables = new EnumMap<>(LogicalDatastoreType.class);
35     @GuardedBy("this")
36     private final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> idToProducer = new TreeMap<>();
37
38     @GuardedBy("this")
39     private ShardingTableEntry lookupShard(final DOMDataTreeIdentifier prefix) {
40         final ShardingTableEntry t = shardingTables.get(prefix.getDatastoreType());
41         if (t == null) {
42             return null;
43         }
44
45         return t.lookup(prefix.getRootIdentifier());
46     }
47
48     @GuardedBy("this")
49     private void storeShard(final DOMDataTreeIdentifier prefix, final ShardRegistration<?> reg) {
50         ShardingTableEntry t = shardingTables.get(prefix.getDatastoreType());
51         if (t == null) {
52             t = new ShardingTableEntry();
53             shardingTables.put(prefix.getDatastoreType(), t);
54         }
55
56         t.store(prefix.getRootIdentifier(), reg);
57     }
58
59     void removeShard(final ShardRegistration<?> reg) {
60         final DOMDataTreeIdentifier prefix = reg.getPrefix();
61         final ShardRegistration<?> parentReg;
62
63         synchronized (this) {
64             final ShardingTableEntry t = shardingTables.get(prefix.getDatastoreType());
65             if (t == null) {
66                 LOG.warn("Shard registration {} points to non-existent table", reg);
67                 return;
68             }
69
70             t.remove(prefix.getRootIdentifier());
71             parentReg = lookupShard(prefix).getRegistration();
72
73             /*
74              * FIXME: adjust all producers and listeners. This is tricky, as we need different
75              * locking strategy, simply because we risk AB/BA deadlock with a producer being split
76              * off from a producer.
77              */
78         }
79
80         if (parentReg != null) {
81             parentReg.getInstance().onChildDetached(prefix, reg.getInstance());
82         }
83     }
84
85     @Override
86     public <T extends DOMDataTreeShard> ListenerRegistration<T> registerDataTreeShard(final DOMDataTreeIdentifier prefix, final T shard) throws DOMDataTreeShardingConflictException {
87         final ShardRegistration<T> reg;
88         final ShardRegistration<?> parentReg;
89
90         synchronized (this) {
91             /*
92              * Lookup the parent shard (e.g. the one which currently matches the prefix),
93              * and if it exists, check if its registration prefix does not collide with
94              * this registration.
95              */
96             final ShardingTableEntry parent = lookupShard(prefix);
97             if (parent != null) {
98                 parentReg = parent.getRegistration();
99                 if (parentReg != null && prefix.equals(parentReg.getPrefix())) {
100                     throw new DOMDataTreeShardingConflictException(String.format(
101                             "Prefix %s is already occupied by shard %s", prefix, parentReg.getInstance()));
102                 }
103             } else {
104                 parentReg = null;
105             }
106
107             // FIXME: wrap the shard in a proper adaptor based on implemented interface
108
109             reg = new ShardRegistration<T>(this, prefix, shard);
110
111             storeShard(prefix, reg);
112
113             // FIXME: update any producers/registrations
114         }
115
116         // Notify the parent shard
117         if (parentReg != null) {
118             parentReg.getInstance().onChildAttached(prefix, shard);
119         }
120
121         return reg;
122     }
123
124     @GuardedBy("this")
125     private DOMDataTreeProducer findProducer(final DOMDataTreeIdentifier subtree) {
126         for (final Entry<DOMDataTreeIdentifier, DOMDataTreeProducer> e : idToProducer.entrySet()) {
127             if (e.getKey().contains(subtree)) {
128                 return e.getValue();
129             }
130         }
131
132         return null;
133     }
134
135     synchronized void destroyProducer(final ShardedDOMDataTreeProducer producer) {
136         for (final DOMDataTreeIdentifier s : producer.getSubtrees()) {
137             final DOMDataTreeProducer r = idToProducer.remove(s);
138             if (!producer.equals(r)) {
139                 LOG.error("Removed producer {} on subtree {} while removing {}", r, s, producer);
140             }
141         }
142     }
143
144     @GuardedBy("this")
145     private DOMDataTreeProducer createProducer(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
146         // Record the producer's attachment points
147         final DOMDataTreeProducer ret = ShardedDOMDataTreeProducer.create(this, shardMap);
148         for (final DOMDataTreeIdentifier s : shardMap.keySet()) {
149             idToProducer.put(s, ret);
150         }
151
152         return ret;
153     }
154
155     @Override
156     public synchronized DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
157         Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
158
159         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
160         for (final DOMDataTreeIdentifier s : subtrees) {
161             // Attempting to create a disconnected producer -- all subtrees have to be unclaimed
162             final DOMDataTreeProducer producer = findProducer(s);
163             Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", s, producer);
164
165             shardMap.put(s, lookupShard(s).getRegistration().getInstance());
166         }
167
168         return createProducer(shardMap);
169     }
170
171     synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent, final Collection<DOMDataTreeIdentifier> subtrees) {
172         Preconditions.checkNotNull(parent);
173
174         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
175         for (final DOMDataTreeIdentifier s : subtrees) {
176             shardMap.put(s, lookupShard(s).getRegistration().getInstance());
177         }
178
179         return createProducer(shardMap);
180     }
181
182     @Override
183     public synchronized <T extends DOMDataTreeListener> ListenerRegistration<T> registerListener(final T listener, final Collection<DOMDataTreeIdentifier> subtrees, final boolean allowRxMerges, final Collection<DOMDataTreeProducer> producers) {
184         Preconditions.checkNotNull(listener, "listener");
185         Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees must not be empty.");
186         final ShardedDOMDataTreeListenerContext<T> listenerContext =
187                 ShardedDOMDataTreeListenerContext.create(listener, subtrees, allowRxMerges);
188         try {
189             // FIXME: Add attachment of producers
190             for (DOMDataTreeIdentifier subtree : subtrees) {
191                 DOMDataTreeShard shard = lookupShard(subtree).getRegistration().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 (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     void removeListener(ShardedDOMDataTreeListenerContext<?> listener) {
212         // FIXME: detach producers
213         listener.close();
214     }
215 }