Support for a recurive create in AsyncDocumentTree + Javadoc clean up
Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java b/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
index 136aa6c..976772e 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
@@ -37,81 +37,94 @@
DocumentPath root();
/**
- * Returns the child values for this node.
+ * Returns the children of node at specified path in the form of a mapping from child name to child value.
*
* @param path path to the node
- * @return future for mapping from a child name to its value
- * @throws NoSuchDocumentPathException if the path does not point to a valid node
+ * @return future for mapping from child name to child value
+ * @throws {@code NoSuchDocumentPathException} if the path does not point to a valid node
*/
CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path);
/**
- * Returns a value (and version) of the tree node at specified path.
+ * Returns the value of the tree node at specified path.
*
- * @param path path to node
- * @return future for node value or {@code null} if path does not point to a valid node
+ * @param path path to the node
+ * @return future that will be either be completed with node value or {@code null} if path
+ * does not point to a valid node
*/
CompletableFuture<Versioned<V>> get(DocumentPath path);
/**
* Creates or updates a document tree node.
*
- * @param path path for the node to create or update
- * @param value the non-null value to be associated with the key
- * @return future for the previous mapping or {@code null} if there was no previous mapping. Future will
- * be completed with a NoSuchDocumentPathException if the parent node (for the node to create/update) does not exist
+ * @param path path to the node
+ * @param value the non-null value to be associated with the node
+ * @return future that will either be completed with the previous node value or {@code null} if there was no
+ * node previously at that path. Future will be completed with a {@code IllegalDocumentModificationException}
+ * if the parent node (for the node to create/update) does not exist
*/
CompletableFuture<Versioned<V>> set(DocumentPath path, V value);
/**
* Creates a document tree node if one does not exist already.
*
- * @param path path for the node to create
- * @param value the non-null value to be associated with the key
- * @return future that is completed with {@code true} if the mapping could be added
- * successfully; {@code false} otherwise. Future will be completed with a
- * IllegalDocumentModificationException if the parent node (for the node to create) does not exist
+ * @param path path to the node
+ * @param value the non-null value to be associated with the node
+ * @return future that is completed with {@code true} if the new node was successfully
+ * created. Future will be completed with {@code false} if a node already exists at the specified path.
+ * Future will be completed exceptionally with a {@code IllegalDocumentModificationException} if the parent
+ * node (for the node to create) does not exist
*/
CompletableFuture<Boolean> create(DocumentPath path, V value);
/**
+ * Creates a document tree node recursively by creating all missing intermediate nodes in the path.
+ *
+ * @param path path to the node
+ * @param value the non-null value to be associated with the key
+ * @return future that is completed with {@code true} if the new node was successfully
+ * created. Future will be completed with {@code false} if a node already exists at the specified path
+ */
+ CompletableFuture<Boolean> createRecursive(DocumentPath path, V value);
+
+ /**
* Conditionally updates a tree node if the current version matches a specified version.
*
- * @param path path for the node to create
- * @param newValue the non-null value to be associated with the key
- * @param version current version of the value for update to occur
- * @return future that is completed with {@code true} if the update was made and the tree was
- * modified, {@code false} otherwise.
+ * @param path path to the node
+ * @param newValue the non-null value to be associated with the node
+ * @param version current version of the node for update to occur
+ * @return future that is either completed with {@code true} if the update was made and the tree was
+ * modified or {@code false} if update did not happen
*/
CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version);
/**
- * Conditionally updates a tree node if the current value matches a specified value.
+ * Conditionally updates a tree node if the current node value matches a specified version.
*
- * @param path path for the node to create
- * @param newValue the non-null value to be associated with the key
- * @param currentValue current value for update to occur
- * @return future that is completed with {@code true} if the update was made and the tree was
- * modified, {@code false} otherwise.
+ * @param path path to the node
+ * @param newValue the non-null value to be associated with the node
+ * @param currentValue current value of the node for update to occur
+ * @return future that is either completed with {@code true} if the update was made and the tree was
+ * modified or {@code false} if update did not happen
*/
CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue);
/**
* Removes the node with the specified path.
*
- * @param path path for the node to remove
+ * @param path path to the node
* @return future for the previous value. Future will be completed with a
- * IllegalDocumentModificationException if the node to be removed is either the root
+ * {@code IllegalDocumentModificationException} if the node to be removed is either the root
* node or has one or more children. Future will be completed with a
- * NoSuchDocumentPathException if the node to be removed does not exist
+ * {@code NoSuchDocumentPathException} if the node to be removed does not exist
*/
CompletableFuture<Versioned<V>> removeNode(DocumentPath path);
/**
- * Registers a listener to be notified when a subtree rooted at the specified path
+ * Registers a listener to be notified when the subtree rooted at the specified path
* is modified.
*
- * @param path path to root of subtree to monitor for updates
+ * @param path path to the node
* @param listener listener to be notified
* @return a future that is completed when the operation completes
*/
diff --git a/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java b/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
index 4255b65..e8e4746 100644
--- a/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
+++ b/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
@@ -73,6 +73,17 @@
boolean create(DocumentPath path, V value);
/**
+ * Creates a document tree node by first creating any missing intermediate nodes in the path.
+ *
+ * @param path path for the node to create
+ * @param value the non-null value to be associated with the key
+ * @return returns {@code true} if the mapping could be added successfully, {@code false} if
+ * a node already exists at that path
+ * @throws IllegalDocumentModificationException if {@code path} points to root
+ */
+ boolean createRecursive(DocumentPath path, V value);
+
+ /**
* Conditionally updates a tree node if the current version matches a specified version.
*
* @param path path for the node to create
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
index f58f220..758d9a0 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
@@ -88,6 +88,11 @@
}
@Override
+ public CompletableFuture<Boolean> createRecursive(DocumentPath path, V value) {
+ return backingTree.createRecursive(path, serializer.encode(value));
+ }
+
+ @Override
public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) {
return backingTree.replace(path, serializer.encode(newValue), version);
}
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
index 939d3f0..8f0c73a 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
@@ -17,6 +17,9 @@
package org.onosproject.store.primitives.resources.impl;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.INVALID_PATH;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.OK;
import io.atomix.copycat.client.CopycatClient;
import io.atomix.resource.AbstractResource;
import io.atomix.resource.ResourceTypeInfo;
@@ -107,9 +110,9 @@
public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) {
return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any()))
.thenCompose(result -> {
- if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+ if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
- } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+ } else if (result.status() == ILLEGAL_MODIFICATION) {
return Tools.exceptionalFuture(new IllegalDocumentModificationException());
} else {
return CompletableFuture.completedFuture(result);
@@ -119,16 +122,25 @@
@Override
public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) {
- return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
- .thenCompose(result -> {
- if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
- return Tools.exceptionalFuture(new NoSuchDocumentPathException());
- } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+ return createInternal(path, value)
+ .thenCompose(status -> {
+ if (status == ILLEGAL_MODIFICATION) {
return Tools.exceptionalFuture(new IllegalDocumentModificationException());
- } else {
- return CompletableFuture.completedFuture(result);
}
- }).thenApply(result -> result.created());
+ return CompletableFuture.completedFuture(true);
+ });
+ }
+
+ @Override
+ public CompletableFuture<Boolean> createRecursive(DocumentPath path, byte[] value) {
+ return createInternal(path, value)
+ .thenCompose(status -> {
+ if (status == ILLEGAL_MODIFICATION) {
+ return createRecursive(path.parent(), new byte[0])
+ .thenCompose(r -> createInternal(path, value).thenApply(v -> true));
+ }
+ return CompletableFuture.completedFuture(status == OK);
+ });
}
@Override
@@ -141,9 +153,9 @@
public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) {
return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any()))
.thenCompose(result -> {
- if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+ if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
- } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+ } else if (result.status() == ILLEGAL_MODIFICATION) {
return Tools.exceptionalFuture(new IllegalDocumentModificationException());
} else {
return CompletableFuture.completedFuture(result);
@@ -158,9 +170,9 @@
}
return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any()))
.thenCompose(result -> {
- if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+ if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
- } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+ } else if (result.status() == ILLEGAL_MODIFICATION) {
return Tools.exceptionalFuture(new IllegalDocumentModificationException());
} else {
return CompletableFuture.completedFuture(result);
@@ -191,6 +203,11 @@
return CompletableFuture.completedFuture(null);
}
+ private CompletableFuture<DocumentTreeUpdateResult.Status> createInternal(DocumentPath path, byte[] value) {
+ return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
+ .thenApply(result -> result.status());
+ }
+
private boolean isListening() {
return !eventListeners.isEmpty();
}
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
index 6589a98..a351ad5 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
@@ -106,6 +106,25 @@
}
@Override
+ public boolean createRecursive(DocumentPath path, V value) {
+ checkRootModification(path);
+ DocumentTreeNode<V> node = getNode(path);
+ if (node != null) {
+ return false;
+ }
+ DocumentPath parentPath = path.parent();
+ if (getNode(parentPath) == null) {
+ createRecursive(parentPath, null);
+ }
+ DefaultDocumentTreeNode<V> parentNode = getNode(parentPath);
+ if (parentNode == null) {
+ throw new IllegalDocumentModificationException();
+ }
+ parentNode.addChild(simpleName(path), value, versionSupplier.get());
+ return true;
+ }
+
+ @Override
public boolean replace(DocumentPath path, V newValue, long version) {
checkRootModification(path);
DocumentTreeNode<V> node = getNode(path);
diff --git a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
index 653b61c..dbaabd8 100644
--- a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
+++ b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
@@ -93,6 +93,24 @@
}
/**
+ * Tests recursive create.
+ */
+ @Test
+ public void testRecursiveCreate() throws Throwable {
+ AtomixDocumentTree tree = createAtomixClient().getResource(UUID.randomUUID().toString(),
+ AtomixDocumentTree.class).join();
+ tree.createRecursive(DocumentPath.from("root.a.b.c"), "abc".getBytes()).join();
+ Versioned<byte[]> a = tree.get(DocumentPath.from("root.a")).join();
+ assertArrayEquals(new byte[0], a.value());
+
+ Versioned<byte[]> ab = tree.get(DocumentPath.from("root.a.b")).join();
+ assertArrayEquals(new byte[0], ab.value());
+
+ Versioned<byte[]> abc = tree.get(DocumentPath.from("root.a.b.c")).join();
+ assertArrayEquals("abc".getBytes(), abc.value());
+ }
+
+ /**
* Tests set.
*/
@Test
diff --git a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
index f111197..60a95d9 100644
--- a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
+++ b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
@@ -49,6 +49,21 @@
Assert.assertTrue(tree.create(path("root.a.b"), "baz"));
}
+ @Test
+ public void testCreateRecursive() {
+ DocumentTree<String> tree = new DefaultDocumentTree<>();
+ tree.createRecursive(path("root.a.b.c"), "bar");
+ Assert.assertEquals(tree.get(path("root.a.b.c")).value(), "bar");
+ Assert.assertNull(tree.get(path("root.a.b")).value());
+ Assert.assertNull(tree.get(path("root.a")).value());
+ }
+
+ @Test(expected = IllegalDocumentModificationException.class)
+ public void testCreateRecursiveRoot() {
+ DocumentTree<String> tree = new DefaultDocumentTree<>();
+ tree.createRecursive(path("root"), "bar");;
+ }
+
@Test(expected = IllegalDocumentModificationException.class)
public void testCreateNodeFailure() {
DocumentTree<String> tree = new DefaultDocumentTree<>();