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 { ...@@ -37,81 +37,94 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive {
37 DocumentPath root(); 37 DocumentPath root();
38 38
39 /** 39 /**
40 - * Returns the child values for this node. 40 + * Returns the children of node at specified path in the form of a mapping from child name to child value.
41 * 41 *
42 * @param path path to the node 42 * @param path path to the node
43 - * @return future for mapping from a child name to its value 43 + * @return future for mapping from child name to child value
44 - * @throws NoSuchDocumentPathException if the path does not point to a valid node 44 + * @throws {@code NoSuchDocumentPathException} if the path does not point to a valid node
45 */ 45 */
46 CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path); 46 CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path);
47 47
48 /** 48 /**
49 - * Returns a value (and version) of the tree node at specified path. 49 + * Returns the value of the tree node at specified path.
50 * 50 *
51 - * @param path path to node 51 + * @param path path to the node
52 - * @return future for node value or {@code null} if path does not point to a valid node 52 + * @return future that will be either be completed with node value or {@code null} if path
53 + * does not point to a valid node
53 */ 54 */
54 CompletableFuture<Versioned<V>> get(DocumentPath path); 55 CompletableFuture<Versioned<V>> get(DocumentPath path);
55 56
56 /** 57 /**
57 * Creates or updates a document tree node. 58 * Creates or updates a document tree node.
58 * 59 *
59 - * @param path path for the node to create or update 60 + * @param path path to the node
60 - * @param value the non-null value to be associated with the key 61 + * @param value the non-null value to be associated with the node
61 - * @return future for the previous mapping or {@code null} if there was no previous mapping. Future will 62 + * @return future that will either be completed with the previous node value or {@code null} if there was no
62 - * be completed with a NoSuchDocumentPathException if the parent node (for the node to create/update) does not exist 63 + * node previously at that path. Future will be completed with a {@code IllegalDocumentModificationException}
64 + * if the parent node (for the node to create/update) does not exist
63 */ 65 */
64 CompletableFuture<Versioned<V>> set(DocumentPath path, V value); 66 CompletableFuture<Versioned<V>> set(DocumentPath path, V value);
65 67
66 /** 68 /**
67 * Creates a document tree node if one does not exist already. 69 * Creates a document tree node if one does not exist already.
68 * 70 *
69 - * @param path path for the node to create 71 + * @param path path to the node
70 - * @param value the non-null value to be associated with the key 72 + * @param value the non-null value to be associated with the node
71 - * @return future that is completed with {@code true} if the mapping could be added 73 + * @return future that is completed with {@code true} if the new node was successfully
72 - * successfully; {@code false} otherwise. Future will be completed with a 74 + * created. Future will be completed with {@code false} if a node already exists at the specified path.
73 - * IllegalDocumentModificationException if the parent node (for the node to create) does not exist 75 + * Future will be completed exceptionally with a {@code IllegalDocumentModificationException} if the parent
76 + * node (for the node to create) does not exist
74 */ 77 */
75 CompletableFuture<Boolean> create(DocumentPath path, V value); 78 CompletableFuture<Boolean> create(DocumentPath path, V value);
76 79
77 /** 80 /**
81 + * Creates a document tree node recursively by creating all missing intermediate nodes in the path.
82 + *
83 + * @param path path to the node
84 + * @param value the non-null value to be associated with the key
85 + * @return future that is completed with {@code true} if the new node was successfully
86 + * created. Future will be completed with {@code false} if a node already exists at the specified path
87 + */
88 + CompletableFuture<Boolean> createRecursive(DocumentPath path, V value);
89 +
90 + /**
78 * Conditionally updates a tree node if the current version matches a specified version. 91 * Conditionally updates a tree node if the current version matches a specified version.
79 * 92 *
80 - * @param path path for the node to create 93 + * @param path path to the node
81 - * @param newValue the non-null value to be associated with the key 94 + * @param newValue the non-null value to be associated with the node
82 - * @param version current version of the value for update to occur 95 + * @param version current version of the node for update to occur
83 - * @return future that is completed with {@code true} if the update was made and the tree was 96 + * @return future that is either completed with {@code true} if the update was made and the tree was
84 - * modified, {@code false} otherwise. 97 + * modified or {@code false} if update did not happen
85 */ 98 */
86 CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version); 99 CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version);
87 100
88 /** 101 /**
89 - * Conditionally updates a tree node if the current value matches a specified value. 102 + * Conditionally updates a tree node if the current node value matches a specified version.
90 * 103 *
91 - * @param path path for the node to create 104 + * @param path path to the node
92 - * @param newValue the non-null value to be associated with the key 105 + * @param newValue the non-null value to be associated with the node
93 - * @param currentValue current value for update to occur 106 + * @param currentValue current value of the node for update to occur
94 - * @return future that is completed with {@code true} if the update was made and the tree was 107 + * @return future that is either completed with {@code true} if the update was made and the tree was
95 - * modified, {@code false} otherwise. 108 + * modified or {@code false} if update did not happen
96 */ 109 */
97 CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue); 110 CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue);
98 111
99 /** 112 /**
100 * Removes the node with the specified path. 113 * Removes the node with the specified path.
101 * 114 *
102 - * @param path path for the node to remove 115 + * @param path path to the node
103 * @return future for the previous value. Future will be completed with a 116 * @return future for the previous value. Future will be completed with a
104 - * IllegalDocumentModificationException if the node to be removed is either the root 117 + * {@code IllegalDocumentModificationException} if the node to be removed is either the root
105 * node or has one or more children. Future will be completed with a 118 * node or has one or more children. Future will be completed with a
106 - * NoSuchDocumentPathException if the node to be removed does not exist 119 + * {@code NoSuchDocumentPathException} if the node to be removed does not exist
107 */ 120 */
108 CompletableFuture<Versioned<V>> removeNode(DocumentPath path); 121 CompletableFuture<Versioned<V>> removeNode(DocumentPath path);
109 122
110 /** 123 /**
111 - * Registers a listener to be notified when a subtree rooted at the specified path 124 + * Registers a listener to be notified when the subtree rooted at the specified path
112 * is modified. 125 * is modified.
113 * 126 *
114 - * @param path path to root of subtree to monitor for updates 127 + * @param path path to the node
115 * @param listener listener to be notified 128 * @param listener listener to be notified
116 * @return a future that is completed when the operation completes 129 * @return a future that is completed when the operation completes
117 */ 130 */
......
...@@ -73,6 +73,17 @@ public interface DocumentTree<V> { ...@@ -73,6 +73,17 @@ public interface DocumentTree<V> {
73 boolean create(DocumentPath path, V value); 73 boolean create(DocumentPath path, V value);
74 74
75 /** 75 /**
76 + * Creates a document tree node by first creating any missing intermediate nodes in the path.
77 + *
78 + * @param path path for the node to create
79 + * @param value the non-null value to be associated with the key
80 + * @return returns {@code true} if the mapping could be added successfully, {@code false} if
81 + * a node already exists at that path
82 + * @throws IllegalDocumentModificationException if {@code path} points to root
83 + */
84 + boolean createRecursive(DocumentPath path, V value);
85 +
86 + /**
76 * Conditionally updates a tree node if the current version matches a specified version. 87 * Conditionally updates a tree node if the current version matches a specified version.
77 * 88 *
78 * @param path path for the node to create 89 * @param path path for the node to create
......
...@@ -88,6 +88,11 @@ public class DefaultDistributedDocumentTree<V> implements AsyncDocumentTree<V> { ...@@ -88,6 +88,11 @@ public class DefaultDistributedDocumentTree<V> implements AsyncDocumentTree<V> {
88 } 88 }
89 89
90 @Override 90 @Override
91 + public CompletableFuture<Boolean> createRecursive(DocumentPath path, V value) {
92 + return backingTree.createRecursive(path, serializer.encode(value));
93 + }
94 +
95 + @Override
91 public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) { 96 public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) {
92 return backingTree.replace(path, serializer.encode(newValue), version); 97 return backingTree.replace(path, serializer.encode(newValue), version);
93 } 98 }
......
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
17 package org.onosproject.store.primitives.resources.impl; 17 package org.onosproject.store.primitives.resources.impl;
18 18
19 import static com.google.common.base.Preconditions.checkNotNull; 19 import static com.google.common.base.Preconditions.checkNotNull;
20 +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION;
21 +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.INVALID_PATH;
22 +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.OK;
20 import io.atomix.copycat.client.CopycatClient; 23 import io.atomix.copycat.client.CopycatClient;
21 import io.atomix.resource.AbstractResource; 24 import io.atomix.resource.AbstractResource;
22 import io.atomix.resource.ResourceTypeInfo; 25 import io.atomix.resource.ResourceTypeInfo;
...@@ -107,9 +110,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> ...@@ -107,9 +110,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
107 public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) { 110 public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) {
108 return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any())) 111 return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any()))
109 .thenCompose(result -> { 112 .thenCompose(result -> {
110 - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { 113 + if (result.status() == INVALID_PATH) {
111 return Tools.exceptionalFuture(new NoSuchDocumentPathException()); 114 return Tools.exceptionalFuture(new NoSuchDocumentPathException());
112 - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { 115 + } else if (result.status() == ILLEGAL_MODIFICATION) {
113 return Tools.exceptionalFuture(new IllegalDocumentModificationException()); 116 return Tools.exceptionalFuture(new IllegalDocumentModificationException());
114 } else { 117 } else {
115 return CompletableFuture.completedFuture(result); 118 return CompletableFuture.completedFuture(result);
...@@ -119,16 +122,25 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> ...@@ -119,16 +122,25 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
119 122
120 @Override 123 @Override
121 public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) { 124 public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) {
122 - return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any())) 125 + return createInternal(path, value)
123 - .thenCompose(result -> { 126 + .thenCompose(status -> {
124 - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { 127 + if (status == ILLEGAL_MODIFICATION) {
125 - return Tools.exceptionalFuture(new NoSuchDocumentPathException());
126 - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
127 return Tools.exceptionalFuture(new IllegalDocumentModificationException()); 128 return Tools.exceptionalFuture(new IllegalDocumentModificationException());
128 - } else {
129 - return CompletableFuture.completedFuture(result);
130 } 129 }
131 - }).thenApply(result -> result.created()); 130 + return CompletableFuture.completedFuture(true);
131 + });
132 + }
133 +
134 + @Override
135 + public CompletableFuture<Boolean> createRecursive(DocumentPath path, byte[] value) {
136 + return createInternal(path, value)
137 + .thenCompose(status -> {
138 + if (status == ILLEGAL_MODIFICATION) {
139 + return createRecursive(path.parent(), new byte[0])
140 + .thenCompose(r -> createInternal(path, value).thenApply(v -> true));
141 + }
142 + return CompletableFuture.completedFuture(status == OK);
143 + });
132 } 144 }
133 145
134 @Override 146 @Override
...@@ -141,9 +153,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> ...@@ -141,9 +153,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
141 public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) { 153 public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) {
142 return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any())) 154 return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any()))
143 .thenCompose(result -> { 155 .thenCompose(result -> {
144 - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { 156 + if (result.status() == INVALID_PATH) {
145 return Tools.exceptionalFuture(new NoSuchDocumentPathException()); 157 return Tools.exceptionalFuture(new NoSuchDocumentPathException());
146 - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { 158 + } else if (result.status() == ILLEGAL_MODIFICATION) {
147 return Tools.exceptionalFuture(new IllegalDocumentModificationException()); 159 return Tools.exceptionalFuture(new IllegalDocumentModificationException());
148 } else { 160 } else {
149 return CompletableFuture.completedFuture(result); 161 return CompletableFuture.completedFuture(result);
...@@ -158,9 +170,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> ...@@ -158,9 +170,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
158 } 170 }
159 return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any())) 171 return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any()))
160 .thenCompose(result -> { 172 .thenCompose(result -> {
161 - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { 173 + if (result.status() == INVALID_PATH) {
162 return Tools.exceptionalFuture(new NoSuchDocumentPathException()); 174 return Tools.exceptionalFuture(new NoSuchDocumentPathException());
163 - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { 175 + } else if (result.status() == ILLEGAL_MODIFICATION) {
164 return Tools.exceptionalFuture(new IllegalDocumentModificationException()); 176 return Tools.exceptionalFuture(new IllegalDocumentModificationException());
165 } else { 177 } else {
166 return CompletableFuture.completedFuture(result); 178 return CompletableFuture.completedFuture(result);
...@@ -191,6 +203,11 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> ...@@ -191,6 +203,11 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
191 return CompletableFuture.completedFuture(null); 203 return CompletableFuture.completedFuture(null);
192 } 204 }
193 205
206 + private CompletableFuture<DocumentTreeUpdateResult.Status> createInternal(DocumentPath path, byte[] value) {
207 + return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
208 + .thenApply(result -> result.status());
209 + }
210 +
194 private boolean isListening() { 211 private boolean isListening() {
195 return !eventListeners.isEmpty(); 212 return !eventListeners.isEmpty();
196 } 213 }
......
...@@ -106,6 +106,25 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { ...@@ -106,6 +106,25 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
106 } 106 }
107 107
108 @Override 108 @Override
109 + public boolean createRecursive(DocumentPath path, V value) {
110 + checkRootModification(path);
111 + DocumentTreeNode<V> node = getNode(path);
112 + if (node != null) {
113 + return false;
114 + }
115 + DocumentPath parentPath = path.parent();
116 + if (getNode(parentPath) == null) {
117 + createRecursive(parentPath, null);
118 + }
119 + DefaultDocumentTreeNode<V> parentNode = getNode(parentPath);
120 + if (parentNode == null) {
121 + throw new IllegalDocumentModificationException();
122 + }
123 + parentNode.addChild(simpleName(path), value, versionSupplier.get());
124 + return true;
125 + }
126 +
127 + @Override
109 public boolean replace(DocumentPath path, V newValue, long version) { 128 public boolean replace(DocumentPath path, V newValue, long version) {
110 checkRootModification(path); 129 checkRootModification(path);
111 DocumentTreeNode<V> node = getNode(path); 130 DocumentTreeNode<V> node = getNode(path);
......
...@@ -93,6 +93,24 @@ public class AtomixDocumentTreeTest extends AtomixTestBase { ...@@ -93,6 +93,24 @@ public class AtomixDocumentTreeTest extends AtomixTestBase {
93 } 93 }
94 94
95 /** 95 /**
96 + * Tests recursive create.
97 + */
98 + @Test
99 + public void testRecursiveCreate() throws Throwable {
100 + AtomixDocumentTree tree = createAtomixClient().getResource(UUID.randomUUID().toString(),
101 + AtomixDocumentTree.class).join();
102 + tree.createRecursive(DocumentPath.from("root.a.b.c"), "abc".getBytes()).join();
103 + Versioned<byte[]> a = tree.get(DocumentPath.from("root.a")).join();
104 + assertArrayEquals(new byte[0], a.value());
105 +
106 + Versioned<byte[]> ab = tree.get(DocumentPath.from("root.a.b")).join();
107 + assertArrayEquals(new byte[0], ab.value());
108 +
109 + Versioned<byte[]> abc = tree.get(DocumentPath.from("root.a.b.c")).join();
110 + assertArrayEquals("abc".getBytes(), abc.value());
111 + }
112 +
113 + /**
96 * Tests set. 114 * Tests set.
97 */ 115 */
98 @Test 116 @Test
......
...@@ -49,6 +49,21 @@ public class DefaultDocumentTreeTest { ...@@ -49,6 +49,21 @@ public class DefaultDocumentTreeTest {
49 Assert.assertTrue(tree.create(path("root.a.b"), "baz")); 49 Assert.assertTrue(tree.create(path("root.a.b"), "baz"));
50 } 50 }
51 51
52 + @Test
53 + public void testCreateRecursive() {
54 + DocumentTree<String> tree = new DefaultDocumentTree<>();
55 + tree.createRecursive(path("root.a.b.c"), "bar");
56 + Assert.assertEquals(tree.get(path("root.a.b.c")).value(), "bar");
57 + Assert.assertNull(tree.get(path("root.a.b")).value());
58 + Assert.assertNull(tree.get(path("root.a")).value());
59 + }
60 +
61 + @Test(expected = IllegalDocumentModificationException.class)
62 + public void testCreateRecursiveRoot() {
63 + DocumentTree<String> tree = new DefaultDocumentTree<>();
64 + tree.createRecursive(path("root"), "bar");;
65 + }
66 +
52 @Test(expected = IllegalDocumentModificationException.class) 67 @Test(expected = IllegalDocumentModificationException.class)
53 public void testCreateNodeFailure() { 68 public void testCreateNodeFailure() {
54 DocumentTree<String> tree = new DefaultDocumentTree<>(); 69 DocumentTree<String> tree = new DefaultDocumentTree<>();
......