Fix warnings in AbstractDOMShardTreeChangePublisher
[mdsal.git] / dom / mdsal-dom-inmemory-datastore / src / main / java / org / opendaylight / mdsal / dom / store / inmemory / AbstractDOMShardTreeChangePublisher.java
index fc4ad229d978c76cf92a9cd0bf6d494ba8f88855..250494180c62bdcae2807ebc97956a11581714ef 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.mdsal.dom.store.inmemory;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -18,9 +19,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
 import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
 import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
 import org.opendaylight.mdsal.dom.spi.store.AbstractDOMStoreTreeChangePublisher;
@@ -28,20 +29,29 @@ import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher implements DOMStoreTreeChangePublisher {
+abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher
+        implements DOMStoreTreeChangePublisher {
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
 
-    private YangInstanceIdentifier shardPath;
+    private final YangInstanceIdentifier shardPath;
     private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
     private final DataTree dataTree;
 
@@ -54,7 +64,8 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
     }
 
     @Override
-    public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
+    public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
+            registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
         takeLock();
         try {
             return setupListenerContext(path, listener);
@@ -63,36 +74,95 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
         }
     }
 
-    private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
+    private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
+            setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
         // we need to register the listener registration path based on the shards root
         // we have to strip the shard path from the listener path and then register
-        YangInstanceIdentifier strippedIdentifier =  listenerPath;
+        YangInstanceIdentifier strippedIdentifier = listenerPath;
         if (!shardPath.isEmpty()) {
             strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
         }
 
-        final DOMDataTreeListenerWithSubshards subshardListener = new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
-        final AbstractDOMDataTreeChangeListenerRegistration<L> reg = setupContextWithoutSubshards(strippedIdentifier, subshardListener);
+        final DOMDataTreeListenerWithSubshards subshardListener =
+                new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
+        final AbstractDOMDataTreeChangeListenerRegistration<L> reg =
+                setupContextWithoutSubshards(strippedIdentifier, subshardListener);
 
         for (final ChildShardContext maybeAffected : childShards.values()) {
             if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
-                // consumer has a subshard somewhere on lower level
+                // consumer has initialDataChangeEvent subshard somewhere on lower level
                 // register to the notification manager with snapshot and forward child notifications to parent
                 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
                 subshardListener.addSubshard(maybeAffected);
             } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
                 // bind path is inside subshard
-                // TODO can this happen? seems like in ShardedDOMDataTree we are already registering to the lowest shard possible
-                throw new UnsupportedOperationException("Listener should be registered directly into a subshard");
+                // TODO can this happen? seems like in ShardedDOMDataTree we are
+                // already registering to the lowest shard possible
+                throw new UnsupportedOperationException("Listener should be registered directly "
+                        + "into initialDataChangeEvent subshard");
             }
         }
+
+        initialDataChangeEvent(listenerPath, listener);
+
         return reg;
     }
 
-    private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath, final DOMDataTreeListenerWithSubshards listener) {
+    private <L extends DOMDataTreeChangeListener> void initialDataChangeEvent(
+            final YangInstanceIdentifier listenerPath, final L listener) {
+        // FIXME Add support for wildcard listeners
+        final Optional<NormalizedNode<?, ?>> preExistingData = dataTree.takeSnapshot()
+                .readNode(YangInstanceIdentifier.create(stripShardPath(listenerPath)));
+        final DataTreeCandidate initialCandidate;
+
+        if (preExistingData.isPresent()) {
+            final NormalizedNode<?, ?> data = preExistingData.get();
+            Preconditions.checkState(data instanceof DataContainerNode,
+                    "Expected DataContainer node, but was {}", data.getClass());
+            // if we are listening on root of some shard we still get
+            // empty normalized node, root is always present
+            if (((DataContainerNode<?>) data).getValue().isEmpty()) {
+                initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
+                        new EmptyDataTreeCandidateNode(data.getIdentifier()));
+            } else {
+                initialCandidate = DataTreeCandidates.fromNormalizedNode(listenerPath,
+                        translateRootShardIdentifierToListenerPath(listenerPath, preExistingData.get()));
+            }
+        } else {
+            initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
+                    new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument()));
+        }
+
+        listener.onDataTreeChanged(Collections.singleton(initialCandidate));
+    }
+
+    private static NormalizedNode<?, ?> translateRootShardIdentifierToListenerPath(
+            final YangInstanceIdentifier listenerPath, final NormalizedNode<?, ?> node) {
+        if (listenerPath.isEmpty()) {
+            return node;
+        }
+
+        final NormalizedNodeBuilder nodeBuilder;
+        if (node instanceof ContainerNode) {
+            nodeBuilder = ImmutableContainerNodeBuilder.create().withValue(((ContainerNode) node).getValue());
+        } else if (node instanceof MapEntryNode) {
+            nodeBuilder = ImmutableMapEntryNodeBuilder.create().withValue(((MapEntryNode) node).getValue());
+        } else {
+            throw new IllegalArgumentException("Expected ContainerNode or MapEntryNode, but was " + node.getClass());
+        }
+        nodeBuilder.withNodeIdentifier(listenerPath.getLastPathArgument());
+        return nodeBuilder.build();
+    }
+
+    private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
+            setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath,
+                    final DOMDataTreeListenerWithSubshards listener) {
         LOG.debug("Registering root listener at {}", listenerPath);
-        final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(listenerPath.getPathArguments());
-        final AbstractDOMDataTreeChangeListenerRegistration<L> registration = new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
+        final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node =
+                findNodeFor(listenerPath.getPathArguments());
+        @SuppressWarnings("unchecked")
+        final AbstractDOMDataTreeChangeListenerRegistration<L> registration =
+                new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
             @Override
             protected void removeRegistration() {
                 listener.close();
@@ -126,7 +196,6 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
 
     private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
 
-        // TODO should we synchronize the access to the dataTree snapshots?
         private final DataTree dataTree;
         private final YangInstanceIdentifier listenerPath;
         private final DOMDataTreeChangeListener delegate;
@@ -144,30 +213,31 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
 
         @Override
         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
-            LOG.debug("Received data changed {}", changes.iterator().next());
-            final DataTreeCandidate newCandidate = applyChanges(changes);
-            delegate.onDataTreeChanged(Collections.singleton(newCandidate));
+            LOG.debug("Received data changed {}", changes);
+            delegate.onDataTreeChanged(changes);
         }
 
         void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
-            onDataTreeChanged(changes.stream()
+            final List<DataTreeCandidate> newCandidates = changes.stream()
                     .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
-                    .collect(Collectors.toList()));
+                    .collect(Collectors.toList());
+            delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
         }
 
         void addSubshard(final ChildShardContext context) {
             Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
-                    "All subshards that are a part of ListenerContext need to be listenable");
+                    "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
 
             final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
             // since this is going into subshard we want to listen for ALL changes in the subshard
             registrations.put(context.getPrefix().getRootIdentifier(),
-                    listenableShard.registerTreeChangeListener(context.getPrefix().getRootIdentifier(),
-                            changes -> onDataTreeChanged(context.getPrefix().getRootIdentifier(), changes)));
+                listenableShard.registerTreeChangeListener(
+                        context.getPrefix().getRootIdentifier(), changes -> onDataTreeChanged(
+                                context.getPrefix().getRootIdentifier(), changes)));
         }
 
         void close() {
-            for (ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
+            for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
                 registration.close();
             }
             registrations.clear();
@@ -182,7 +252,7 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
             modification.ready();
             try {
                 dataTree.validate(modification);
-            } catch (DataValidationFailedException e) {
+            } catch (final DataValidationFailedException e) {
                 LOG.error("Validation failed for built modification", e);
                 throw new RuntimeException("Notification validation failed", e);
             }
@@ -190,12 +260,60 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
             // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
             DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
             for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
-                // there should be no null pointers since we wouldn't get a notification change
-                // if there was no node modified at the listener's location
                 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
             }
 
+            if (modifiedChild == null) {
+                modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
+            }
+
             return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
         }
     }
+
+    private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
+
+        private final PathArgument identifier;
+
+        EmptyDataTreeCandidateNode(final PathArgument identifier) {
+            this.identifier = Preconditions.checkNotNull(identifier, "Identifier should not be null");
+        }
+
+        @Nonnull
+        @Override
+        public PathArgument getIdentifier() {
+            return identifier;
+        }
+
+        @Nonnull
+        @Override
+        public Collection<DataTreeCandidateNode> getChildNodes() {
+            return Collections.emptySet();
+        }
+
+        @Nullable
+        @Override
+        public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public ModificationType getModificationType() {
+            return ModificationType.UNMODIFIED;
+        }
+
+        @Nonnull
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataAfter() {
+            return Optional.absent();
+        }
+
+        @Nonnull
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataBefore() {
+            return Optional.absent();
+        }
+    }
+
 }
\ No newline at end of file