BUG-4359: use WRITE-implied metadata 24/27924/3
authorRobert Varga <rovarga@cisco.com>
Mon, 5 Oct 2015 17:30:13 +0000 (19:30 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 8 Oct 2015 17:08:35 +0000 (17:08 +0000)
When we apply an operation under a WRITE node, we need to look up the
metadata based on that write to correctly understand what the effect of
this operation is.

This is important when we modify data introduced by the WRITE node, as
without this lookup a WRITE/DELETE combination on the child will result
in a NONE logical operation, which means data introduced in the WRITE
node will not be modified and seemingly reappear.

Change-Id: I5a5a67470f1f8baafc7a71069341dead073ba84a
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModificationCursor.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java

index a667bfd98eff4e0d899391d3ecd8fd3cd7518ea6..48b6fe502a0ac840618254f3b7021c1313755773 100644 (file)
@@ -75,7 +75,7 @@ final class InMemoryDataTreeModification extends AbstractCursorAware implements
     public void merge(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
         checkSealed();
         checkIdentifierReferencesData(path, data);
-        resolveModificationFor(path).merge(data);
+        resolveModificationFor(path).merge(data, version);
     }
 
     @Override
@@ -159,7 +159,7 @@ final class InMemoryDataTreeModification extends AbstractCursorAware implements
             operation = potential.get();
             ++i;
 
-            modification = modification.modifyChild(pathArg, operation.getChildPolicy());
+            modification = modification.modifyChild(pathArg, operation.getChildPolicy(), version);
         }
 
         return OperationWithModification.from(operation, modification);
index 17216ee92f491156c02826f0e4801d7939bd5a5b..e13073af02d0a88ca2bf9aaf31ddb882b0cd664f 100644 (file)
@@ -34,7 +34,8 @@ final class InMemoryDataTreeModificationCursor extends AbstractCursor<InMemoryDa
         final Optional<ModificationApplyOperation> potential = op.getApplyOperation().getChild(child);
         if (potential.isPresent()) {
             final ModificationApplyOperation operation = potential.get();
-            final ModifiedNode modification = op.getModification().modifyChild(child, operation.getChildPolicy());
+            final ModifiedNode modification = op.getModification().modifyChild(child, operation.getChildPolicy(),
+                getParent().getVersion());
 
             return OperationWithModification.from(operation, modification);
         }
@@ -101,7 +102,7 @@ final class InMemoryDataTreeModificationCursor extends AbstractCursor<InMemoryDa
     public void merge(final PathArgument child, final NormalizedNode<?, ?> data) {
         ensureNotClosed();
         InMemoryDataTreeModification.checkIdentifierReferencesData(child, data);
-        resolveChildModification(child).merge(data);
+        resolveChildModification(child).merge(data, getParent().getVersion());
     }
 
     @Override
index 01e2299bc9de159c33aa17ab1005b0d6081b434c..315464e999cbf7e86b98c330d19a8618b9736316 100644 (file)
@@ -16,9 +16,12 @@ import javax.annotation.Nonnull;
 import javax.annotation.concurrent.NotThreadSafe;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
 
 /**
  * Node Modification Node and Tree
@@ -57,6 +60,9 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
     private NormalizedNode<?, ?> value;
     private ModificationType modType;
 
+    // Alternative history introduced in WRITE nodes. Instantiated when we touch any child underneath such a node.
+    private TreeNode writtenOriginal;
+
     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final ChildTrackingPolicy childPolicy) {
         this.identifier = identifier;
         this.original = original;
@@ -100,6 +106,54 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
         return Optional.<ModifiedNode> fromNullable(children.get(child));
     }
 
+    private Optional<TreeNode> metadataFromSnapshot(@Nonnull final PathArgument child) {
+        return original.isPresent() ? original.get().getChild(child) : Optional.<TreeNode>absent();
+    }
+
+    private Optional<TreeNode> metadataFromData(@Nonnull final PathArgument child, final Version modVersion) {
+        if (writtenOriginal == null) {
+            // Lazy instantiation, as we do not want do this for all writes. We are using the modification's version
+            // here, as that version is what the SchemaAwareApplyOperation will see when dealing with the resulting
+            // modifications.
+            writtenOriginal = TreeNodeFactory.createTreeNode(value, modVersion);
+        }
+
+        return writtenOriginal.getChild(child);
+    }
+
+    /**
+     * Determine the base tree node we are going to apply the operation to. This is not entirely trivial because
+     * both DELETE and WRITE operations unconditionally detach their descendants from the original snapshot, so we need
+     * to take the current node's operation into account.
+     *
+     * @param child Child we are looking to modify
+     * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
+     * @return Before-image tree node as observed by that child.
+     */
+    private Optional<TreeNode> findOriginalMetadata(@Nonnull final PathArgument child, final Version modVersion) {
+        switch (operation) {
+        case DELETE:
+            // DELETE implies non-presence
+            return Optional.absent();
+        case NONE:
+        case TOUCH:
+            return metadataFromSnapshot(child);
+        case MERGE:
+            // MERGE is half-way between TOUCH and WRITE. If the child exists in data, it behaves as a WRITE, otherwise
+            // it behaves as a TOUCH
+            if (NormalizedNodes.findNode(value, child).isPresent()) {
+                return metadataFromData(child, modVersion);
+            } else {
+                return metadataFromSnapshot(child);
+            }
+        case WRITE:
+            // WRITE implies presence based on written data
+            return metadataFromData(child, modVersion);
+        }
+
+        throw new IllegalStateException("Unhandled node operation " + operation);
+    }
+
     /**
      *
      * Returns child modification if child was modified, creates {@link ModifiedNode}
@@ -110,10 +164,12 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
      *
      * @param child child identifier, may not be null
      * @param childPolicy child tracking policy for the node we are looking for
+     * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
      * @return {@link ModifiedNode} for specified child, with {@link #getOriginal()}
      *         containing child metadata if child was present in original data.
      */
-    ModifiedNode modifyChild(@Nonnull final PathArgument child, @Nonnull final ChildTrackingPolicy childPolicy) {
+    ModifiedNode modifyChild(@Nonnull final PathArgument child, @Nonnull final ChildTrackingPolicy childPolicy,
+            @Nonnull final Version modVersion) {
         clearSnapshot();
         if (operation == LogicalOperation.NONE) {
             updateOperationType(LogicalOperation.TOUCH);
@@ -123,13 +179,8 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
             return potential;
         }
 
-        final Optional<TreeNode> currentMetadata;
-        if (original.isPresent()) {
-            final TreeNode orig = original.get();
-            currentMetadata = orig.getChild(child);
-        } else {
-            currentMetadata = Optional.absent();
-        }
+        final Optional<TreeNode> currentMetadata = findOriginalMetadata(child, modVersion);
+
 
         final ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, childPolicy);
         children.put(child, newlyCreated);
@@ -227,6 +278,7 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
      */
     void seal(final ModificationApplyOperation schema) {
         clearSnapshot();
+        writtenOriginal = null;
 
         // A TOUCH node without any children is a no-op
         switch (operation) {
@@ -259,6 +311,9 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode<Modif
     private void updateOperationType(final LogicalOperation type) {
         operation = type;
         modType = null;
+
+        // Make sure we do not reuse previously-instantiated data-derived metadata
+        writtenOriginal = null;
         clearSnapshot();
     }
 
index 98e0db6ab28a515f81b8c00701713c87ca7094a6..fbfb499c6dcd484caccebc9a41014f54c6dc8c1b 100644 (file)
@@ -40,7 +40,7 @@ final class OperationWithModification {
         applyOperation.verifyStructure(value, false);
     }
 
-    private void recursiveMerge(final NormalizedNode<?,?> data) {
+    private void recursiveMerge(final NormalizedNode<?,?> data, final Version version) {
         if (data instanceof NormalizedNodeContainer) {
             @SuppressWarnings({ "rawtypes", "unchecked" })
             final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> dataContainer =
@@ -69,20 +69,20 @@ final class OperationWithModification {
                         }
                     } else {
                         // Not present, issue a write
-                        forChild(childId).write(c);
+                        forChild(childId, version).write(c);
                     }
                 }
             }
             for (final NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
                 final PathArgument childId = child.getIdentifier();
-                forChild(childId).recursiveMerge(child);
+                forChild(childId, version).recursiveMerge(child, version);
             }
         }
 
         modification.merge(data);
     }
 
-    void merge(final NormalizedNode<?, ?> data) {
+    void merge(final NormalizedNode<?, ?> data, final Version version) {
         /*
          * A merge operation will end up overwriting parts of the tree, retaining others. We want to
          * make sure we do not validate the complete resulting structure, but rather just what was
@@ -93,7 +93,7 @@ final class OperationWithModification {
          * FIXME: Should be this moved to recursive merge and run for each node?
          */
         applyOperation.verifyStructure(data, false);
-        recursiveMerge(data);
+        recursiveMerge(data, version);
     }
 
     void delete() {
@@ -151,12 +151,13 @@ final class OperationWithModification {
         return new OperationWithModification(operation, modification);
     }
 
-    private OperationWithModification forChild(final PathArgument childId) {
+    private OperationWithModification forChild(final PathArgument childId, final Version version) {
         final Optional<ModificationApplyOperation> maybeChildOp = applyOperation.getChild(childId);
-        Preconditions.checkArgument(maybeChildOp.isPresent(), "Attempted to apply operation to non-existent child %s", childId);
+        Preconditions.checkArgument(maybeChildOp.isPresent(),
+            "Attempted to apply operation to non-existent child %s", childId);
 
         final ModificationApplyOperation childOp = maybeChildOp.get();
-        final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy());
+        final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy(), version);
 
         return from(childOp, childMod);
     }