Madan Jampani

Support for a recurive create in AsyncDocumentTree + Javadoc clean up

Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
......@@ -37,81 +37,94 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
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
*/
......
......@@ -73,6 +73,17 @@ public interface DocumentTree<V> {
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
......
......@@ -88,6 +88,11 @@ public class DefaultDistributedDocumentTree<V> implements AsyncDocumentTree<V> {
}
@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);
}
......
......@@ -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 class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
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 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
@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 class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
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 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
}
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 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
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();
}
......
......@@ -106,6 +106,25 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
}
@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);
......
......@@ -93,6 +93,24 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
}
/**
* 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
......
......@@ -49,6 +49,21 @@ public class DefaultDocumentTreeTest {
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<>();
......