Madan Jampani
Committed by Gerrit Code Review

Allow null values for DocumentTree nodes

Change-Id: I88a12727751c6d82843a7b6a9a2e753da1500c99
......@@ -49,8 +49,8 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
* Returns the value of the tree node at specified path.
*
* @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
* @return future that will be either be completed with node's {@link Versioned versioned} value
* or {@code null} if path does not point to a valid node
*/
CompletableFuture<Versioned<V>> get(DocumentPath path);
......@@ -58,9 +58,10 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
* Creates or updates a document tree node.
*
* @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}
* @param value value to be associated with the node ({@code null} is a valid value)
* @return future that will either be completed with the previous {@link Versioned versioned}
* 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);
......@@ -81,7 +82,7 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
* 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
* @param value value to be associated with the node ({@code null} is a valid value)
* @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
*/
......@@ -91,10 +92,10 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
* Conditionally updates a tree node if the current version matches a specified version.
*
* @param path path to the node
* @param newValue the non-null value to be associated with the node
* @param newValue value to associate with the node ({@code null} is a valid value)
* @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
* @return future that is either completed with {@code true} if the update was made
* or {@code false} if update did not happen
*/
CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version);
......@@ -102,10 +103,10 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
* Conditionally updates a tree node if the current node value matches a specified version.
*
* @param path path to the node
* @param newValue the non-null value to be associated with the node
* @param newValue value to associate with the node ({@code null} is a valid value)
* @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
* @return future that is either completed with {@code true} if the update was made
* or {@code false} if update did not happen
*/
CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue);
......
......@@ -21,6 +21,7 @@ import io.atomix.manager.util.ResourceManagerTypeResolver;
import io.atomix.variables.internal.LongCommands;
import java.util.Arrays;
import java.util.Optional;
import org.onlab.util.Match;
import org.onosproject.cluster.Leader;
......@@ -110,6 +111,7 @@ public final class CatalystSerializers {
serializer.register(ImmutableList.of().getClass(), factory);
serializer.register(ImmutableList.of("a").getClass(), factory);
serializer.register(Arrays.asList().getClass(), factory);
serializer.register(Optional.class, factory);
serializer.resolve(new LongCommands.TypeResolver());
serializer.resolve(new AtomixConsistentMapCommands.TypeResolver());
......
......@@ -27,6 +27,7 @@ import io.atomix.resource.ResourceTypeInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
......@@ -108,7 +109,7 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
@Override
public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) {
return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any()))
return client.submit(new Update(checkNotNull(path), Optional.ofNullable(value), Match.any(), Match.any()))
.thenCompose(result -> {
if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
......@@ -136,7 +137,7 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
return createInternal(path, value)
.thenCompose(status -> {
if (status == ILLEGAL_MODIFICATION) {
return createRecursive(path.parent(), new byte[0])
return createRecursive(path.parent(), null)
.thenCompose(r -> createInternal(path, value).thenApply(v -> true));
}
return CompletableFuture.completedFuture(status == OK);
......@@ -145,13 +146,19 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
@Override
public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, long version) {
return client.submit(new Update(checkNotNull(path), newValue, Match.any(), Match.ifValue(version)))
return client.submit(new Update(checkNotNull(path),
Optional.ofNullable(newValue),
Match.any(),
Match.ifValue(version)))
.thenApply(result -> result.updated());
}
@Override
public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) {
return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any()))
return client.submit(new Update(checkNotNull(path),
Optional.ofNullable(newValue),
Match.ifValue(currentValue),
Match.any()))
.thenCompose(result -> {
if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
......@@ -168,7 +175,7 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
if (path.equals(DocumentPath.from("root"))) {
return Tools.exceptionalFuture(new IllegalDocumentModificationException());
}
return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any()))
return client.submit(new Update(checkNotNull(path), null, Match.any(), Match.ifNotNull()))
.thenCompose(result -> {
if (result.status() == INVALID_PATH) {
return Tools.exceptionalFuture(new NoSuchDocumentPathException());
......@@ -204,7 +211,7 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
}
private CompletableFuture<DocumentTreeUpdateResult.Status> createInternal(DocumentPath path, byte[] value) {
return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
return client.submit(new Update(checkNotNull(path), Optional.ofNullable(value), Match.any(), Match.ifNull()))
.thenApply(result -> result.status());
}
......
......@@ -26,6 +26,7 @@ import io.atomix.copycat.Command;
import io.atomix.copycat.Query;
import java.util.Map;
import java.util.Optional;
import org.onlab.util.Match;
import org.onosproject.store.service.DocumentPath;
......@@ -139,7 +140,7 @@ public class AtomixDocumentTreeCommands {
@SuppressWarnings("serial")
public static class Update extends DocumentTreeCommand<DocumentTreeUpdateResult<byte[]>> {
private byte[] value;
private Optional<byte[]> value;
private Match<byte[]> valueMatch;
private Match<Long> versionMatch;
......@@ -150,14 +151,14 @@ public class AtomixDocumentTreeCommands {
this.versionMatch = null;
}
public Update(DocumentPath path, byte[] value, Match<byte[]> valueMatch, Match<Long> versionMatch) {
public Update(DocumentPath path, Optional<byte[]> value, Match<byte[]> valueMatch, Match<Long> versionMatch) {
super(path);
this.value = value;
this.valueMatch = valueMatch;
this.versionMatch = versionMatch;
}
public byte[] value() {
public Optional<byte[]> value() {
return value;
}
......
......@@ -244,7 +244,7 @@ public class AtomixDocumentTreeState
@Override
public byte[] value() {
return commit.operation().value();
return commit.operation().value().orElse(null);
}
@Override
......
......@@ -89,6 +89,10 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
Versioned<byte[]> ac = tree.get(DocumentPath.from("root.a.c")).join();
assertArrayEquals("ac".getBytes(), ac.value());
tree.create(DocumentPath.from("root.x"), null).join();
Versioned<byte[]> x = tree.get(DocumentPath.from("root.x")).join();
assertNull(x.value());
}
/**
......@@ -100,10 +104,10 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
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());
assertArrayEquals(null, a.value());
Versioned<byte[]> ab = tree.get(DocumentPath.from("root.a.b")).join();
assertArrayEquals(new byte[0], ab.value());
assertArrayEquals(null, ab.value());
Versioned<byte[]> abc = tree.get(DocumentPath.from("root.a.b.c")).join();
assertArrayEquals("abc".getBytes(), abc.value());
......@@ -131,6 +135,10 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
tree.set(DocumentPath.from("root.a.b"), "newAB".getBytes()).join();
Versioned<byte[]> newAB = tree.get(DocumentPath.from("root.a.b")).join();
assertArrayEquals("newAB".getBytes(), newAB.value());
tree.set(DocumentPath.from("root.x"), null).join();
Versioned<byte[]> x = tree.get(DocumentPath.from("root.x")).join();
assertNull(x.value());
}
/**
......@@ -199,6 +207,11 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
Versioned<byte[]> a = tree.removeNode(DocumentPath.from("root.a")).join();
assertArrayEquals("a".getBytes(), a.value());
assertNull(tree.get(DocumentPath.from("root.a")).join());
tree.create(DocumentPath.from("root.x"), null).join();
Versioned<byte[]> x = tree.removeNode(DocumentPath.from("root.x")).join();
assertNull(x.value());
assertNull(tree.get(DocumentPath.from("root.a.x")).join());
}
/**
......