Enable mdsal-dom-broker spotbugs
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / ShardedDOMDataTree.java
index 5a585dfe2a09eaaeef53e1fe3d6d77670ee231d8..71e63d80942bd953d2e907b75450c9e79f870259 100644 (file)
@@ -7,8 +7,18 @@
  */
 package org.opendaylight.mdsal.dom.broker;
 
-import com.google.common.base.Preconditions;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ClassToInstanceMap;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -18,11 +28,14 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeLoopException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeServiceExtension;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingConflictException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingService;
 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTable;
 import org.opendaylight.mdsal.dom.spi.DOMDataTreePrefixTableEntry;
+import org.opendaylight.mdsal.dom.spi.shard.DOMDataTreeListenerAggregator;
+import org.opendaylight.mdsal.dom.spi.shard.ListenableDOMDataTreeShard;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
@@ -33,14 +46,13 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTree.class);
 
     @GuardedBy("this")
-    private final DOMDataTreePrefixTable<ShardRegistration<?>> shards = DOMDataTreePrefixTable.create();
+    private final DOMDataTreePrefixTable<DOMDataTreeShardRegistration<?>> shards = DOMDataTreePrefixTable.create();
     @GuardedBy("this")
     private final DOMDataTreePrefixTable<DOMDataTreeProducer> producers = DOMDataTreePrefixTable.create();
 
-
-    void removeShard(final ShardRegistration<?> reg) {
+    void removeShard(final DOMDataTreeShardRegistration<?> reg) {
         final DOMDataTreeIdentifier prefix = reg.getPrefix();
-        final ShardRegistration<?> parentReg;
+        final DOMDataTreeShardRegistration<?> parentReg;
 
         synchronized (this) {
             shards.remove(prefix);
@@ -59,9 +71,20 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
     }
 
     @Override
-    public <T extends DOMDataTreeShard> ListenerRegistration<T> registerDataTreeShard(final DOMDataTreeIdentifier prefix, final T shard) throws DOMDataTreeShardingConflictException {
-        final ShardRegistration<T> reg;
-        final ShardRegistration<?> parentReg;
+    public <T extends DOMDataTreeShard> DOMDataTreeShardRegistration<T> registerDataTreeShard(
+            final DOMDataTreeIdentifier prefix, final T shard, final DOMDataTreeProducer producer)
+                    throws DOMDataTreeShardingConflictException {
+        checkArgument(producer instanceof ShardedDOMDataTreeProducer, "Unsupported producer %s", producer);
+        final ShardedDOMDataTreeProducer prod = (ShardedDOMDataTreeProducer) producer;
+
+        final DOMDataTreeIdentifier firstSubtree = Iterables.getOnlyElement(prod.getSubtrees());
+        checkArgument(firstSubtree != null, "Producer that is used to verify namespace claim can"
+                + " only claim a single namespace");
+        checkArgument(prefix.equals(firstSubtree), "Trying to register shard to a different namespace"
+                + " than the producer has claimed");
+
+        final DOMDataTreeShardRegistration<T> reg;
+        final DOMDataTreeShardRegistration<?> parentReg;
 
         synchronized (this) {
             /*
@@ -69,7 +92,7 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
              * and if it exists, check if its registration prefix does not collide with
              * this registration.
              */
-            final DOMDataTreePrefixTableEntry<ShardRegistration<?>> parent = shards.lookup(prefix);
+            final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> parent = shards.lookup(prefix);
             if (parent != null) {
                 parentReg = parent.getValue();
                 if (parentReg != null && prefix.equals(parentReg.getPrefix())) {
@@ -82,11 +105,11 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
 
             // FIXME: wrap the shard in a proper adaptor based on implemented interface
 
-            reg = new ShardRegistration<T>(this, prefix, shard);
+            reg = new DOMDataTreeShardRegistration<>(this, prefix, shard);
 
             shards.store(prefix, reg);
 
-            // FIXME: update any producers/registrations
+            prod.subshardAdded(Collections.singletonMap(prefix, shard));
         }
 
         // Notify the parent shard
@@ -100,7 +123,7 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
     @GuardedBy("this")
     private DOMDataTreeProducer findProducer(final DOMDataTreeIdentifier subtree) {
 
-        DOMDataTreePrefixTableEntry<DOMDataTreeProducer> producerEntry = producers.lookup(subtree);
+        final DOMDataTreePrefixTableEntry<DOMDataTreeProducer> producerEntry = producers.lookup(subtree);
         if (producerEntry != null) {
             return producerEntry.getValue();
         }
@@ -113,12 +136,18 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
         }
     }
 
+    @Override
+    public ClassToInstanceMap<DOMDataTreeServiceExtension> getExtensions() {
+        return ImmutableClassToInstanceMap.of();
+    }
+
     @GuardedBy("this")
-    private DOMDataTreeProducer createProducer(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
+    private DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees,
+            final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
         // Record the producer's attachment points
-        final DOMDataTreeProducer ret = ShardedDOMDataTreeProducer.create(this, shardMap);
-        for (final DOMDataTreeIdentifier s : shardMap.keySet()) {
-            producers.store(s, ret);
+        final DOMDataTreeProducer ret = ShardedDOMDataTreeProducer.create(this, subtrees, shardMap);
+        for (final DOMDataTreeIdentifier subtree : subtrees) {
+            producers.store(subtree, ret);
         }
 
         return ret;
@@ -126,74 +155,119 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
 
     @Override
     public synchronized DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
-        Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
+        checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
 
         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
-        for (final DOMDataTreeIdentifier s : subtrees) {
+        for (final DOMDataTreeIdentifier subtree : subtrees) {
             // Attempting to create a disconnected producer -- all subtrees have to be unclaimed
-            final DOMDataTreeProducer producer = findProducer(s);
-            Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", s, producer);
+            final DOMDataTreeProducer producer = findProducer(subtree);
+            checkArgument(producer == null, "Subtree %s is attached to producer %s", subtree, producer);
 
-            shardMap.put(s, shards.lookup(s).getValue().getInstance());
+            final DOMDataTreePrefixTableEntry<DOMDataTreeShardRegistration<?>> possibleShardReg =
+                    shards.lookup(subtree);
+            if (possibleShardReg != null && possibleShardReg.getValue() != null) {
+                shardMap.put(subtree, possibleShardReg.getValue().getInstance());
+            }
         }
 
-        return createProducer(shardMap);
+        return createProducer(subtrees, shardMap);
     }
 
-    synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent, final Collection<DOMDataTreeIdentifier> subtrees) {
-        Preconditions.checkNotNull(parent);
+    synchronized DOMDataTreeProducer createProducer(final ShardedDOMDataTreeProducer parent,
+            final Collection<DOMDataTreeIdentifier> subtrees) {
+        requireNonNull(parent);
 
         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
         for (final DOMDataTreeIdentifier s : subtrees) {
             shardMap.put(s, shards.lookup(s).getValue().getInstance());
         }
 
-        return createProducer(shardMap);
+        return createProducer(subtrees, shardMap);
     }
 
+    @SuppressWarnings({ "checkstyle:IllegalCatch", "checkstyle:hiddenField" })
     @Override
     public synchronized <T extends DOMDataTreeListener> ListenerRegistration<T> registerListener(final T listener,
             final Collection<DOMDataTreeIdentifier> subtrees, final boolean allowRxMerges,
             final Collection<DOMDataTreeProducer> producers) throws DOMDataTreeLoopException {
-        Preconditions.checkNotNull(listener, "listener");
-        Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees must not be empty.");
-        final ShardedDOMDataTreeListenerContext<T> listenerContext =
-                ShardedDOMDataTreeListenerContext.create(listener, subtrees, allowRxMerges);
-        try {
-            // FIXME: Add attachment of producers
-            for (DOMDataTreeProducer producer : producers) {
-                Preconditions.checkArgument(producer instanceof ShardedDOMDataTreeProducer);
-                ShardedDOMDataTreeProducer castedProducer = ((ShardedDOMDataTreeProducer) producer);
-                simpleLoopCheck(subtrees, castedProducer.getSubtrees());
-                // FIXME: We should also unbound listeners
-                castedProducer.boundToListener(listenerContext);
-            }
+        requireNonNull(listener, "listener");
+
+        // Cross-check specified trees for exclusivity and eliminate duplicates, noDupSubtrees is effectively a Set
+        final Collection<DOMDataTreeIdentifier> noDupSubtrees;
+        switch (subtrees.size()) {
+            case 0:
+                throw new IllegalArgumentException("Subtrees must not be empty.");
+            case 1:
+                noDupSubtrees = subtrees;
+                break;
+            default:
+                // Check subtrees for mutual inclusion, this is an O(N**2) operation
+                for (DOMDataTreeIdentifier toCheck : subtrees) {
+                    for (DOMDataTreeIdentifier against : subtrees) {
+                        if (!toCheck.equals(against)) {
+                            checkArgument(!toCheck.contains(against), "Subtree %s contains subtree %s", toCheck,
+                                against);
+                        }
+                    }
+                }
 
-            for (DOMDataTreeIdentifier subtree : subtrees) {
-                DOMDataTreeShard shard = shards.lookup(subtree).getValue().getInstance();
-                // FIXME: What should we do if listener is wildcard? And shards are on per
-                // node basis?
-                Preconditions.checkArgument(shard instanceof DOMStoreTreeChangePublisher,
-                        "Subtree %s does not point to listenable subtree.", subtree);
+                noDupSubtrees = ImmutableSet.copyOf(subtrees);
+        }
 
-                listenerContext.register(subtree, (DOMStoreTreeChangePublisher) shard);
-            }
-        } catch (Exception e) {
-            listenerContext.close();
-            throw e;
+        LOG.trace("Requested registration of listener {} to subtrees {}", listener, noDupSubtrees);
+
+        // Lookup shards corresponding to subtrees and construct a map of which subtrees we want from which shard
+        final ListMultimap<DOMDataTreeShardRegistration<?>, DOMDataTreeIdentifier> needed =
+                ArrayListMultimap.create();
+        for (final DOMDataTreeIdentifier subtree : subtrees) {
+            final DOMDataTreeShardRegistration<?> reg = verifyNotNull(shards.lookup(subtree).getValue());
+            needed.put(reg, subtree);
+        }
+
+        LOG.trace("Listener {} is attaching to shards {}", listener, needed);
+
+        // Sanity check: all selected shards have to support one of the listening interfaces
+        needed.asMap().forEach((reg, trees) -> {
+            final DOMDataTreeShard shard = reg.getInstance();
+            checkArgument(shard instanceof ListenableDOMDataTreeShard
+                || shard instanceof DOMStoreTreeChangePublisher, "Subtrees %s do not point to listenable subtree.",
+                trees);
+        });
+
+        // Sanity check: all producers have to come from this implementation and must not form loops
+        for (DOMDataTreeProducer producer : producers) {
+            checkArgument(producer instanceof ShardedDOMDataTreeProducer);
+            simpleLoopCheck(subtrees, ((ShardedDOMDataTreeProducer) producer).getSubtrees());
         }
+
+        final ListenerRegistration<?> underlyingRegistration = createRegisteredListener(listener, needed.asMap(),
+            allowRxMerges, producers);
         return new AbstractListenerRegistration<T>(listener) {
             @Override
             protected void removeRegistration() {
-                ShardedDOMDataTree.this.removeListener(listenerContext);
+                ShardedDOMDataTree.this.removeListener(listener);
+                underlyingRegistration.close();
             }
         };
     }
 
-    private static void simpleLoopCheck(Collection<DOMDataTreeIdentifier> listen, Set<DOMDataTreeIdentifier> writes)
-            throws DOMDataTreeLoopException {
-        for(DOMDataTreeIdentifier listenPath : listen) {
-            for (DOMDataTreeIdentifier writePath : writes) {
+    private static ListenerRegistration<?> createRegisteredListener(final DOMDataTreeListener userListener,
+            final Map<DOMDataTreeShardRegistration<?>, Collection<DOMDataTreeIdentifier>> needed,
+            final boolean allowRxMerges, final Collection<DOMDataTreeProducer> producers) {
+        // FIXME: Add attachment of producers
+        for (final DOMDataTreeProducer producer : producers) {
+            // FIXME: We should also unbound listeners
+            ((ShardedDOMDataTreeProducer) producer).bindToListener(userListener);
+        }
+
+        return DOMDataTreeListenerAggregator.aggregateIfNeeded(userListener, needed, allowRxMerges,
+            DOMDataTreeShardRegistration::getInstance);
+    }
+
+    private static void simpleLoopCheck(final Collection<DOMDataTreeIdentifier> listen,
+            final Set<DOMDataTreeIdentifier> writes) throws DOMDataTreeLoopException {
+        for (final DOMDataTreeIdentifier listenPath : listen) {
+            for (final DOMDataTreeIdentifier writePath : writes) {
                 if (listenPath.contains(writePath)) {
                     throw new DOMDataTreeLoopException(String.format(
                             "Listener must not listen on parent (%s), and also writes child (%s)", listenPath,
@@ -207,8 +281,7 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
         }
     }
 
-    void removeListener(ShardedDOMDataTreeListenerContext<?> listener) {
+    void removeListener(final DOMDataTreeListener listener) {
         // FIXME: detach producers
-        listener.close();
     }
 }