Support for a recurive create in AsyncDocumentTree + Javadoc clean up
Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
Showing
7 changed files
with
143 additions
and
45 deletions
... | @@ -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<>(); | ... | ... |
-
Please register or login to post a comment