BUG-2882: introduce cursor-based modification API 29/19129/1
authorRobert Varga <rovarga@cisco.com>
Sun, 19 Apr 2015 02:36:43 +0000 (04:36 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Mon, 27 Apr 2015 08:27:23 +0000 (08:27 +0000)
This patch introduces the notion of a DataTreeSnapshotCursor and a
DataTreeModificationCurser as its mutating variant. The use of cursors
can lean to potential savings when applying multiple operations which
share a common root nested deeper in the data tree.

Change-Id: Iaed2c3d2d0260c97f4dfa36410b1c0944ba37c6c
Signed-off-by: Robert Varga <rovarga@cisco.com>
(cherry picked from commit ccb71b5a08b7a6d5d03b21492c1e8f968c434a22)

yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/BackendFailedException.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeModification.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeSnapshot.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidates.java
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModificationCursor.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/SynchronizedDataTreeModification.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java

diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/BackendFailedException.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/BackendFailedException.java
new file mode 100644 (file)
index 0000000..e683947
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.api.schema.tree;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Exception thrown when the backed of a {@link DataTreeSnapshotCursor}
+ * detects an errors which prevents it from completing the requested operation.
+ */
+@Beta
+public class BackendFailedException extends IllegalStateException {
+    private static final long serialVersionUID = 1L;
+
+    public BackendFailedException(final String message) {
+        super(message);
+    }
+
+    public BackendFailedException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeModification.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeModification.java
new file mode 100644 (file)
index 0000000..9154d0a
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.api.schema.tree;
+
+import com.google.common.annotations.Beta;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * A {@link DataTreeModification} which allows creation of a {@link DataTreeModificationCursor}.
+ */
+@Beta
+public interface CursorAwareDataTreeModification extends DataTreeModification, CursorAwareDataTreeSnapshot {
+    /**
+     * Create a new {@link DataTreeModificationCursor} at specified path. May fail
+     * if specified path does not exist. It is a programming error to use normal
+     *
+     *
+     * @param path Path at which the cursor is to be anchored
+     * @return A new cursor, or null if the path does not exist.
+     * @throws IllegalStateException if there is another cursor currently open,
+     *                               or the modification is already {@link #ready()}.
+     */
+    @Override
+    @Nullable DataTreeModificationCursor createCursor(@Nonnull YangInstanceIdentifier path);
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeSnapshot.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeSnapshot.java
new file mode 100644 (file)
index 0000000..a9252ba
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.api.schema.tree;
+
+import com.google.common.annotations.Beta;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * A {@link DataTreeSnapshot} which allows creation of a {@link DataTreeSnapshotCursor}.
+ */
+@Beta
+public interface CursorAwareDataTreeSnapshot extends DataTreeSnapshot {
+    /**
+     * Create a new {@link DataTreeSnapshotCursor} at specified path. May fail
+     * if specified path does not exist.
+     *
+     * @param path Path at which the cursor is to be anchored
+     * @return A new cursor, or null if the path does not exist.
+     * @throws IllegalStateException if there is another cursor currently open.
+     */
+    @Nullable DataTreeSnapshotCursor createCursor(@Nonnull YangInstanceIdentifier path);
+}
index 04e3b204bcbc12f7ae3ab795bb558810e066559b..99256397b0e9197f9cb156060c8c03fb7b76278f 100644 (file)
@@ -32,7 +32,13 @@ public final class DataTreeCandidates {
     }
 
     public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
-        applyNode(modification, candidate.getRootPath(), candidate.getRootNode());
+        if (modification instanceof CursorAwareDataTreeModification) {
+            try (DataTreeModificationCursor cursor = ((CursorAwareDataTreeModification) modification).createCursor(candidate.getRootPath())) {
+                applyNode(cursor, candidate.getRootNode());
+            }
+        } else {
+            applyNode(modification, candidate.getRootPath(), candidate.getRootNode());
+        }
     }
 
     private static void applyNode(final DataTreeModification modification, final YangInstanceIdentifier path, final DataTreeCandidateNode node) {
@@ -59,4 +65,27 @@ public final class DataTreeCandidates {
             throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
         }
     }
+
+    private static void applyNode(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
+        switch (node.getModificationType()) {
+        case DELETE:
+            cursor.delete(node.getIdentifier());
+            break;
+        case SUBTREE_MODIFIED:
+            cursor.enter(node.getIdentifier());
+            for (DataTreeCandidateNode child : node.getChildNodes()) {
+                applyNode(cursor, child);
+            }
+            cursor.exit();
+            break;
+        case UNMODIFIED:
+            // No-op
+            break;
+        case WRITE:
+            cursor.write(node.getIdentifier(), node.getDataAfter().get());
+            break;
+        default:
+            throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+        }
+    }
 }
index 2d11f106b079391d86f283c34faadfa463b44abf..1ee737805710a1954d03bc89b6fe2395f743599a 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.yangtools.yang.data.api.schema.tree;
 
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
@@ -42,9 +43,20 @@ public interface DataTreeModification extends DataTreeSnapshot {
 
     /**
      * Finish creation of a modification, making it ready for application
-     * to the data tree. Any calls to this object's methods will result
+     * to the data tree. Any calls to this object's methods except
+     * {@link #applyToCursor(DataTreeModificationCursor)} will result
      * in undefined behavior, possibly with an
      * {@link IllegalStateException} being thrown.
      */
     void ready();
+
+    /**
+     * Apply the contents of this modification to a cursor. This can be used
+     * to replicate this modification onto another one. The cursor's position
+     * must match the root of this modification, otherwise performing this
+     * operation will result in undefined behavior.
+     *
+     * @param cursor cursor to which this modification
+     */
+    void applyToCursor(@Nonnull DataTreeModificationCursor cursor);
 }
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModificationCursor.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModificationCursor.java
new file mode 100644 (file)
index 0000000..964ec83
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.api.schema.tree;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Extension to the {@link DataTreeSnapshotCursor} which allows modifying the data tree.
+ * An instance of this interface can be obtained from {@link CursorAwareDataTreeModification}
+ * and modifications made through this interface are staged in the parent modification.
+ */
+@Beta
+public interface DataTreeModificationCursor extends DataTreeSnapshotCursor {
+    /**
+     * Delete the specified child.
+     *
+     * @param child Child identifier
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     */
+    void delete(PathArgument child);
+
+    /**
+     * Merge the specified data with the currently-present data
+     * at specified path.
+     *
+     * @param child Child identifier
+     * @param data Data to be merged
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     */
+    void merge(PathArgument child, NormalizedNode<?, ?> data);
+
+    /**
+     * Replace the data at specified path with supplied data.
+     *
+     * @param child Child identifier
+     * @param data New node data
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     */
+    void write(PathArgument child, NormalizedNode<?, ?> data);
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java
new file mode 100644 (file)
index 0000000..a2ebb4f
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.api.schema.tree;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import javax.annotation.Nonnull;
+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.NormalizedNodeContainer;
+
+/**
+ * A cursor holding a logical position within a {@link DataTreeSnapshot}. It allows
+ * operations relative to that position, as well as moving the position up or down
+ * the tree.
+ */
+@Beta
+public interface DataTreeSnapshotCursor extends AutoCloseable {
+    /**
+     * Move the cursor to the specified child of the current position.
+     *
+     * @param child Child identifier
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     * @throws IllegalArgumentException when specified identifier does not identify
+     *                                  a valid child, or if that child is not an
+     *                                  instance of {@link NormalizedNodeContainer}.
+     */
+    void enter(@Nonnull PathArgument child);
+
+    /**
+     * Move the cursor to the specified child of the current position. This is
+     * the equivalent of multiple invocations of {@link #enter(PathArgument)},
+     * except the operation is performed atomically.
+     *
+     * @param path Nested child identifier
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     * @throws IllegalArgumentException when specified path does not identify
+     *                                  a valid child, or if that child is not an
+     *                                  instance of {@link NormalizedNodeContainer}.
+     */
+    void enter(@Nonnull PathArgument... path);
+
+    /**
+     * Move the cursor to the specified child of the current position. This is
+     * equivalent to {@link #enter(PathArgument...)}, except it takes an {@link Iterable}
+     * argument.
+     *
+     * @param path Nested child identifier
+     * @throws BackendFailedException when implementation-specific errors occurs
+     *                                while servicing the request.
+     * @throws IllegalArgumentException when specified path does not identify
+     *                                  a valid child, or if that child is not an
+     *                                  instance of {@link NormalizedNodeContainer}.
+     */
+    void enter(@Nonnull Iterable<PathArgument> path);
+
+    /**
+     * Move the cursor up to the parent of current position. This is equivalent of
+     * invoking <code>exit(1)</code>.
+     *
+     * @throws IllegalStateException when exiting would violate containment, typically
+     *                               by attempting to exit more levels than previously
+     *                               entered.
+     */
+    void exit();
+
+    /**
+     * Move the cursor up by specified amounts of steps from the current position.
+     * This is equivalent of invoking {@link #exit()} multiple times, except the
+     * operation is performed atomically.
+     *
+     * @param depth number of steps to exit
+     * @throws IllegalArgumentException when depth is negative.
+     * @throws IllegalStateException when exiting would violate containment, typically
+     *                               by attempting to exit more levels than previously
+     *                               entered.
+     */
+    void exit(int depth);
+
+    /**
+     * Read a particular node from the snapshot.
+     *
+     * @param child Child identifier
+     * @return Optional result encapsulating the presence and value of the node
+     * @throws BackendFailedException when implementation-specific error occurs while
+     *                                servicing the request.
+     * @throws IllegalArgumentException when specified path does not identify a valid child.
+     */
+    Optional<NormalizedNode<?, ?>> readNode(@Nonnull PathArgument child);
+
+    /**
+     * Close this cursor. Attempting any further operations on the cursor will lead
+     * to undefined behavior.
+     */
+    @Override
+    void close();
+}
index 81aa6dcd82cde602c98b7f676601cb3037a6335f..8491a42488b1c79a4f896d82c3e62df9f574e1bd 100644 (file)
@@ -57,4 +57,9 @@ public final class SynchronizedDataTreeModification implements DataTreeModificat
     public synchronized void ready() {
         delegate.ready();
     }
+
+    @Override
+    public synchronized void applyToCursor(final DataTreeModificationCursor cursor) {
+        delegate.applyToCursor(cursor);
+    }
 }
index e0e136545803a902dc643fce86284ffa88c8c2a9..2dda1b3f654fc38527d6849b674e6315f6540bad 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
+import java.util.Collection;
 import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -17,6 +18,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum
 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.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
@@ -201,4 +203,49 @@ final class InMemoryDataTreeModification implements DataTreeModification {
     Version getVersion() {
         return version;
     }
+
+    private static void applyChildren(final DataTreeModificationCursor cursor, final ModifiedNode node) {
+        final Collection<ModifiedNode> children = node.getChildren();
+        if (!children.isEmpty()) {
+            cursor.enter(node.getIdentifier());
+            for (ModifiedNode child : children) {
+                applyNode(cursor, child);
+            }
+            cursor.exit();
+        }
+    }
+
+    private static void applyNode(final DataTreeModificationCursor cursor, final ModifiedNode node) {
+        switch (node.getOperation()) {
+        case NONE:
+            break;
+        case DELETE:
+            cursor.delete(node.getIdentifier());
+            break;
+        case MERGE:
+            cursor.merge(node.getIdentifier(), node.getWrittenValue());
+            applyChildren(cursor, node);
+            break;
+        case TOUCH:
+            // TODO: we could improve efficiency of cursor use if we could understand
+            //       nested TOUCH operations. One way of achieving that would be a proxy
+            //       cursor, which would keep track of consecutive enter and exit calls
+            //       and coalesce them.
+            applyChildren(cursor, node);
+            break;
+        case WRITE:
+            cursor.write(node.getIdentifier(), node.getWrittenValue());
+            applyChildren(cursor, node);
+            break;
+        default:
+            throw new IllegalArgumentException("Unhandled node operation " + node.getOperation());
+        }
+    }
+
+    @Override
+    public void applyToCursor(final DataTreeModificationCursor cursor) {
+        for (ModifiedNode child : rootNode.getChildren()) {
+            applyNode(cursor, child);
+        }
+    }
 }