From: Robert Varga Date: Sun, 19 Apr 2015 02:36:43 +0000 (+0200) Subject: BUG-2882: introduce cursor-based modification API X-Git-Tag: release/lithium~102^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=6a78ccff458b4c16f3d5e3b57709376504521388;p=yangtools.git BUG-2882: introduce cursor-based modification API 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 (cherry picked from commit ccb71b5a08b7a6d5d03b21492c1e8f968c434a22) --- 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 index 0000000000..e68394712c --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/BackendFailedException.java @@ -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 index 0000000000..9154d0a273 --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeModification.java @@ -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 index 0000000000..a9252ba347 --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/CursorAwareDataTreeSnapshot.java @@ -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); +} diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidates.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidates.java index 04e3b204bc..99256397b0 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidates.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidates.java @@ -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()); + } + } } diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java index 2d11f106b0..1ee7378057 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java @@ -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 index 0000000000..964ec83cf0 --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModificationCursor.java @@ -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 index 0000000000..a2ebb4fa87 --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshotCursor.java @@ -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 path); + + /** + * Move the cursor up to the parent of current position. This is equivalent of + * invoking exit(1). + * + * @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> readNode(@Nonnull PathArgument child); + + /** + * Close this cursor. Attempting any further operations on the cursor will lead + * to undefined behavior. + */ + @Override + void close(); +} diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/SynchronizedDataTreeModification.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/SynchronizedDataTreeModification.java index 81aa6dcd82..8491a42488 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/SynchronizedDataTreeModification.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/SynchronizedDataTreeModification.java @@ -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); + } } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java index e0e1365458..2dda1b3f65 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java @@ -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 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); + } + } }