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