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