AtomixDocumentTree support for filtering notifications by DocumentPath
Change-Id: I3f4f616bc4f2e488e5433e44f72bcd121b564b0d
Showing
6 changed files
with
239 additions
and
39 deletions
... | @@ -17,10 +17,14 @@ | ... | @@ -17,10 +17,14 @@ |
17 | package org.onosproject.store.service; | 17 | package org.onosproject.store.service; |
18 | 18 | ||
19 | import java.util.Arrays; | 19 | import java.util.Arrays; |
20 | +import java.util.Collection; | ||
20 | import java.util.Iterator; | 21 | import java.util.Iterator; |
21 | import java.util.List; | 22 | import java.util.List; |
22 | import java.util.Objects; | 23 | import java.util.Objects; |
23 | 24 | ||
25 | +import org.apache.commons.collections.CollectionUtils; | ||
26 | +import org.apache.commons.lang.StringUtils; | ||
27 | + | ||
24 | import com.google.common.base.Preconditions; | 28 | import com.google.common.base.Preconditions; |
25 | import com.google.common.collect.ImmutableList; | 29 | import com.google.common.collect.ImmutableList; |
26 | import com.google.common.collect.Lists; | 30 | import com.google.common.collect.Lists; |
... | @@ -103,6 +107,46 @@ public class DocumentPath implements Comparable<DocumentPath> { | ... | @@ -103,6 +107,46 @@ public class DocumentPath implements Comparable<DocumentPath> { |
103 | return ImmutableList.copyOf(pathElements); | 107 | return ImmutableList.copyOf(pathElements); |
104 | } | 108 | } |
105 | 109 | ||
110 | + /** | ||
111 | + * Returns if the specified path belongs to a direct ancestor of the node pointed at by this path. | ||
112 | + * <p> | ||
113 | + * Example: {@code root.a} is a direct ancestor of {@code r.a.b.c}; while {@code r.a.x} is not. | ||
114 | + * | ||
115 | + * @param other other path | ||
116 | + * @return {@code true} is yes; {@code false} otherwise. | ||
117 | + */ | ||
118 | + public boolean isAncestorOf(DocumentPath other) { | ||
119 | + return !other.equals(this) && other.toString().startsWith(toString()); | ||
120 | + } | ||
121 | + | ||
122 | + /** | ||
123 | + * Returns if the specified path is belongs to a subtree rooted this path. | ||
124 | + * <p> | ||
125 | + * Example: {@code root.a.b} and {@code root.a.b.c.d.e} are descendants of {@code r.a.b}; | ||
126 | + * while {@code r.a.x.c} is not. | ||
127 | + * | ||
128 | + * @param other other path | ||
129 | + * @return {@code true} is yes; {@code false} otherwise. | ||
130 | + */ | ||
131 | + public boolean isDescendentOf(DocumentPath other) { | ||
132 | + return other.equals(this) || other.isAncestorOf(this); | ||
133 | + } | ||
134 | + | ||
135 | + /** | ||
136 | + * Returns the path that points to the least common ancestor of the specified | ||
137 | + * collection of paths. | ||
138 | + * @param paths collection of path | ||
139 | + * @return path to least common ancestor | ||
140 | + */ | ||
141 | + public static DocumentPath leastCommonAncestor(Collection<DocumentPath> paths) { | ||
142 | + if (CollectionUtils.isEmpty(paths)) { | ||
143 | + return null; | ||
144 | + } | ||
145 | + return DocumentPath.from(StringUtils.getCommonPrefix(paths.stream() | ||
146 | + .map(DocumentPath::toString) | ||
147 | + .toArray(String[]::new))); | ||
148 | + } | ||
149 | + | ||
106 | @Override | 150 | @Override |
107 | public int hashCode() { | 151 | public int hashCode() { |
108 | return Objects.hash(pathElements); | 152 | return Objects.hash(pathElements); | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +package org.onosproject.store.service; | ||
18 | + | ||
19 | +import static org.junit.Assert.assertEquals; | ||
20 | +import static org.junit.Assert.assertFalse; | ||
21 | +import static org.junit.Assert.assertTrue; | ||
22 | + | ||
23 | +import java.util.Arrays; | ||
24 | + | ||
25 | +import org.junit.Test; | ||
26 | + | ||
27 | +/** | ||
28 | + * Unit tests for {@link DocumentPath}. | ||
29 | + */ | ||
30 | +public class DocumentPathTest { | ||
31 | + | ||
32 | + @Test | ||
33 | + public void testConstruction() { | ||
34 | + DocumentPath path = DocumentPath.from("root.a.b"); | ||
35 | + assertEquals(path.pathElements(), Arrays.asList("root", "a", "b")); | ||
36 | + assertEquals(DocumentPath.from("root.a"), path.parent()); | ||
37 | + } | ||
38 | + | ||
39 | + @Test | ||
40 | + public void testAncestry() { | ||
41 | + DocumentPath path1 = DocumentPath.from("root.a.b"); | ||
42 | + DocumentPath path2 = DocumentPath.from("root.a.d"); | ||
43 | + DocumentPath path3 = DocumentPath.from("root.a.b.c"); | ||
44 | + DocumentPath lca = DocumentPath.leastCommonAncestor(Arrays.asList(path1, path2, path3)); | ||
45 | + assertEquals(DocumentPath.from("root.a"), lca); | ||
46 | + assertTrue(path1.isAncestorOf(path3)); | ||
47 | + assertFalse(path1.isAncestorOf(path2)); | ||
48 | + assertTrue(path3.isDescendentOf(path3)); | ||
49 | + assertTrue(path3.isDescendentOf(path1)); | ||
50 | + assertFalse(path3.isDescendentOf(path2)); | ||
51 | + } | ||
52 | +} |
... | @@ -33,11 +33,11 @@ import java.util.concurrent.Executor; | ... | @@ -33,11 +33,11 @@ import java.util.concurrent.Executor; |
33 | 33 | ||
34 | import org.onlab.util.Match; | 34 | import org.onlab.util.Match; |
35 | import org.onlab.util.Tools; | 35 | import org.onlab.util.Tools; |
36 | -import org.onosproject.store.primitives.resources.impl.AtomixConsistentMapCommands.Unlisten; | ||
37 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Clear; | 36 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Clear; |
38 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Get; | 37 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Get; |
39 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.GetChildren; | 38 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.GetChildren; |
40 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Listen; | 39 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Listen; |
40 | +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Unlisten; | ||
41 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Update; | 41 | import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Update; |
42 | import org.onosproject.store.service.AsyncDocumentTree; | 42 | import org.onosproject.store.service.AsyncDocumentTree; |
43 | import org.onosproject.store.service.DocumentPath; | 43 | import org.onosproject.store.service.DocumentPath; |
... | @@ -56,7 +56,7 @@ import com.google.common.util.concurrent.MoreExecutors; | ... | @@ -56,7 +56,7 @@ import com.google.common.util.concurrent.MoreExecutors; |
56 | public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | 56 | public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
57 | implements AsyncDocumentTree<byte[]> { | 57 | implements AsyncDocumentTree<byte[]> { |
58 | 58 | ||
59 | - private final Map<DocumentTreeListener<byte[]>, Executor> eventListeners = new HashMap<>(); | 59 | + private final Map<DocumentTreeListener<byte[]>, InternalListener> eventListeners = new HashMap<>(); |
60 | public static final String CHANGE_SUBJECT = "changeEvents"; | 60 | public static final String CHANGE_SUBJECT = "changeEvents"; |
61 | 61 | ||
62 | protected AtomixDocumentTree(CopycatClient client, Properties options) { | 62 | protected AtomixDocumentTree(CopycatClient client, Properties options) { |
... | @@ -184,21 +184,21 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -184,21 +184,21 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
184 | public CompletableFuture<Void> addListener(DocumentPath path, DocumentTreeListener<byte[]> listener) { | 184 | public CompletableFuture<Void> addListener(DocumentPath path, DocumentTreeListener<byte[]> listener) { |
185 | checkNotNull(path); | 185 | checkNotNull(path); |
186 | checkNotNull(listener); | 186 | checkNotNull(listener); |
187 | + InternalListener internalListener = new InternalListener(path, listener, MoreExecutors.directExecutor()); | ||
187 | // TODO: Support API that takes an executor | 188 | // TODO: Support API that takes an executor |
188 | - if (isListening()) { | 189 | + if (!eventListeners.containsKey(listener)) { |
189 | - eventListeners.putIfAbsent(listener, MoreExecutors.directExecutor()); | ||
190 | - return CompletableFuture.completedFuture(null); | ||
191 | - } else { | ||
192 | return client.submit(new Listen(path)) | 190 | return client.submit(new Listen(path)) |
193 | - .thenRun(() -> eventListeners.put(listener, MoreExecutors.directExecutor())); | 191 | + .thenRun(() -> eventListeners.put(listener, internalListener)); |
194 | } | 192 | } |
193 | + return CompletableFuture.completedFuture(null); | ||
195 | } | 194 | } |
196 | 195 | ||
197 | @Override | 196 | @Override |
198 | public CompletableFuture<Void> removeListener(DocumentTreeListener<byte[]> listener) { | 197 | public CompletableFuture<Void> removeListener(DocumentTreeListener<byte[]> listener) { |
199 | checkNotNull(listener); | 198 | checkNotNull(listener); |
200 | - if (eventListeners.remove(listener) != null && eventListeners.isEmpty()) { | 199 | + InternalListener internalListener = eventListeners.remove(listener); |
201 | - return client.submit(new Unlisten()).thenApply(v -> null); | 200 | + if (internalListener != null && eventListeners.isEmpty()) { |
201 | + return client.submit(new Unlisten(internalListener.path)).thenApply(v -> null); | ||
202 | } | 202 | } |
203 | return CompletableFuture.completedFuture(null); | 203 | return CompletableFuture.completedFuture(null); |
204 | } | 204 | } |
... | @@ -213,7 +213,26 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -213,7 +213,26 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
213 | } | 213 | } |
214 | 214 | ||
215 | private void processTreeUpdates(List<DocumentTreeEvent<byte[]>> events) { | 215 | private void processTreeUpdates(List<DocumentTreeEvent<byte[]>> events) { |
216 | - events.forEach(event -> | 216 | + events.forEach(event -> eventListeners.values().forEach(listener -> listener.event(event))); |
217 | - eventListeners.forEach((listener, executor) -> executor.execute(() -> listener.event(event)))); | 217 | + } |
218 | + | ||
219 | + private class InternalListener implements DocumentTreeListener<byte[]> { | ||
220 | + | ||
221 | + private final DocumentPath path; | ||
222 | + private final DocumentTreeListener<byte[]> listener; | ||
223 | + private final Executor executor; | ||
224 | + | ||
225 | + public InternalListener(DocumentPath path, DocumentTreeListener<byte[]> listener, Executor executor) { | ||
226 | + this.path = path; | ||
227 | + this.listener = listener; | ||
228 | + this.executor = executor; | ||
229 | + } | ||
230 | + | ||
231 | + @Override | ||
232 | + public void event(DocumentTreeEvent<byte[]> event) { | ||
233 | + if (event.path().isDescendentOf(path)) { | ||
234 | + executor.execute(() -> listener.event(event)); | ||
235 | + } | ||
236 | + } | ||
218 | } | 237 | } |
219 | } | 238 | } | ... | ... |
... | @@ -225,11 +225,10 @@ public class AtomixDocumentTreeCommands { | ... | @@ -225,11 +225,10 @@ public class AtomixDocumentTreeCommands { |
225 | } | 225 | } |
226 | 226 | ||
227 | @Override | 227 | @Override |
228 | - public void writeObject(BufferOutput<?> buffer, Serializer serializer) { | 228 | + public String toString() { |
229 | - } | 229 | + return MoreObjects.toStringHelper(getClass()) |
230 | - | 230 | + .add("path", path()) |
231 | - @Override | 231 | + .toString(); |
232 | - public void readObject(BufferInput<?> buffer, Serializer serializer) { | ||
233 | } | 232 | } |
234 | } | 233 | } |
235 | 234 | ||
... | @@ -248,11 +247,10 @@ public class AtomixDocumentTreeCommands { | ... | @@ -248,11 +247,10 @@ public class AtomixDocumentTreeCommands { |
248 | } | 247 | } |
249 | 248 | ||
250 | @Override | 249 | @Override |
251 | - public void writeObject(BufferOutput<?> buffer, Serializer serializer) { | 250 | + public String toString() { |
252 | - } | 251 | + return MoreObjects.toStringHelper(getClass()) |
253 | - | 252 | + .add("path", path()) |
254 | - @Override | 253 | + .toString(); |
255 | - public void readObject(BufferInput<?> buffer, Serializer serializer) { | ||
256 | } | 254 | } |
257 | } | 255 | } |
258 | 256 | ... | ... |
... | @@ -26,7 +26,10 @@ import io.atomix.copycat.server.storage.snapshot.SnapshotReader; | ... | @@ -26,7 +26,10 @@ import io.atomix.copycat.server.storage.snapshot.SnapshotReader; |
26 | import io.atomix.copycat.server.storage.snapshot.SnapshotWriter; | 26 | import io.atomix.copycat.server.storage.snapshot.SnapshotWriter; |
27 | import io.atomix.resource.ResourceStateMachine; | 27 | import io.atomix.resource.ResourceStateMachine; |
28 | 28 | ||
29 | +import java.util.Arrays; | ||
29 | import java.util.HashMap; | 30 | import java.util.HashMap; |
31 | +import java.util.Iterator; | ||
32 | +import java.util.List; | ||
30 | import java.util.Map; | 33 | import java.util.Map; |
31 | import java.util.Optional; | 34 | import java.util.Optional; |
32 | import java.util.Properties; | 35 | import java.util.Properties; |
... | @@ -52,7 +55,7 @@ import org.onosproject.store.service.Versioned; | ... | @@ -52,7 +55,7 @@ import org.onosproject.store.service.Versioned; |
52 | import org.slf4j.Logger; | 55 | import org.slf4j.Logger; |
53 | 56 | ||
54 | import com.google.common.base.Throwables; | 57 | import com.google.common.base.Throwables; |
55 | -import com.google.common.collect.ImmutableList; | 58 | +import com.google.common.collect.Lists; |
56 | import com.google.common.collect.Maps; | 59 | import com.google.common.collect.Maps; |
57 | import com.google.common.collect.Queues; | 60 | import com.google.common.collect.Queues; |
58 | 61 | ||
... | @@ -64,7 +67,7 @@ public class AtomixDocumentTreeState | ... | @@ -64,7 +67,7 @@ public class AtomixDocumentTreeState |
64 | implements SessionListener, Snapshottable { | 67 | implements SessionListener, Snapshottable { |
65 | 68 | ||
66 | private final Logger log = getLogger(getClass()); | 69 | private final Logger log = getLogger(getClass()); |
67 | - private final Map<Long, Commit<? extends Listen>> listeners = new HashMap<>(); | 70 | + private final Map<Long, SessionListenCommits> listeners = new HashMap<>(); |
68 | private AtomicLong versionCounter = new AtomicLong(0); | 71 | private AtomicLong versionCounter = new AtomicLong(0); |
69 | private final DocumentTree<TreeNodeValue> docTree = new DefaultDocumentTree<>(versionCounter::incrementAndGet); | 72 | private final DocumentTree<TreeNodeValue> docTree = new DefaultDocumentTree<>(versionCounter::incrementAndGet); |
70 | 73 | ||
... | @@ -97,25 +100,23 @@ public class AtomixDocumentTreeState | ... | @@ -97,25 +100,23 @@ public class AtomixDocumentTreeState |
97 | 100 | ||
98 | protected void listen(Commit<? extends Listen> commit) { | 101 | protected void listen(Commit<? extends Listen> commit) { |
99 | Long sessionId = commit.session().id(); | 102 | Long sessionId = commit.session().id(); |
100 | - if (listeners.putIfAbsent(sessionId, commit) != null) { | 103 | + listeners.computeIfAbsent(sessionId, k -> new SessionListenCommits()).add(commit); |
101 | - commit.close(); | ||
102 | - return; | ||
103 | - } | ||
104 | commit.session().onStateChange( | 104 | commit.session().onStateChange( |
105 | state -> { | 105 | state -> { |
106 | if (state == ServerSession.State.CLOSED | 106 | if (state == ServerSession.State.CLOSED |
107 | || state == ServerSession.State.EXPIRED) { | 107 | || state == ServerSession.State.EXPIRED) { |
108 | - Commit<? extends Listen> listener = listeners.remove(sessionId); | 108 | + closeListener(commit.session().id()); |
109 | - if (listener != null) { | ||
110 | - listener.close(); | ||
111 | - } | ||
112 | } | 109 | } |
113 | }); | 110 | }); |
114 | } | 111 | } |
115 | 112 | ||
116 | protected void unlisten(Commit<? extends Unlisten> commit) { | 113 | protected void unlisten(Commit<? extends Unlisten> commit) { |
114 | + Long sessionId = commit.session().id(); | ||
117 | try { | 115 | try { |
118 | - closeListener(commit.session().id()); | 116 | + SessionListenCommits listenCommits = listeners.get(sessionId); |
117 | + if (listenCommits != null) { | ||
118 | + listenCommits.remove(commit); | ||
119 | + } | ||
119 | } finally { | 120 | } finally { |
120 | commit.close(); | 121 | commit.close(); |
121 | } | 122 | } |
... | @@ -261,10 +262,11 @@ public class AtomixDocumentTreeState | ... | @@ -261,10 +262,11 @@ public class AtomixDocumentTreeState |
261 | result.created() ? Type.CREATED : result.newValue() == null ? Type.DELETED : Type.UPDATED, | 262 | result.created() ? Type.CREATED : result.newValue() == null ? Type.DELETED : Type.UPDATED, |
262 | Optional.ofNullable(result.newValue()), | 263 | Optional.ofNullable(result.newValue()), |
263 | Optional.ofNullable(result.oldValue())); | 264 | Optional.ofNullable(result.oldValue())); |
265 | + | ||
264 | listeners.values() | 266 | listeners.values() |
265 | - .forEach(commit -> commit.session() | 267 | + .stream() |
266 | - .publish(AtomixDocumentTree.CHANGE_SUBJECT, | 268 | + .filter(l -> event.path().isDescendentOf(l.leastCommonAncestorPath())) |
267 | - ImmutableList.of(event))); | 269 | + .forEach(listener -> listener.publish(AtomixDocumentTree.CHANGE_SUBJECT, Arrays.asList(event))); |
268 | } | 270 | } |
269 | 271 | ||
270 | @Override | 272 | @Override |
... | @@ -287,9 +289,52 @@ public class AtomixDocumentTreeState | ... | @@ -287,9 +289,52 @@ public class AtomixDocumentTreeState |
287 | } | 289 | } |
288 | 290 | ||
289 | private void closeListener(Long sessionId) { | 291 | private void closeListener(Long sessionId) { |
290 | - Commit<? extends Listen> commit = listeners.remove(sessionId); | 292 | + SessionListenCommits listenCommits = listeners.remove(sessionId); |
291 | - if (commit != null) { | 293 | + if (listenCommits != null) { |
292 | - commit.close(); | 294 | + listenCommits.close(); |
295 | + } | ||
296 | + } | ||
297 | + | ||
298 | + private class SessionListenCommits { | ||
299 | + private final List<Commit<? extends Listen>> commits = Lists.newArrayList(); | ||
300 | + private DocumentPath leastCommonAncestorPath; | ||
301 | + | ||
302 | + public void add(Commit<? extends Listen> commit) { | ||
303 | + commits.add(commit); | ||
304 | + recomputeLeastCommonAncestor(); | ||
305 | + } | ||
306 | + | ||
307 | + public void remove(Commit<? extends Unlisten> commit) { | ||
308 | + // Remove the first listen commit with path matching path in unlisten commit | ||
309 | + Iterator<Commit<? extends Listen>> iterator = commits.iterator(); | ||
310 | + while (iterator.hasNext()) { | ||
311 | + Commit<? extends Listen> listenCommit = iterator.next(); | ||
312 | + if (listenCommit.operation().path().equals(commit.operation().path())) { | ||
313 | + iterator.remove(); | ||
314 | + listenCommit.close(); | ||
315 | + } | ||
316 | + } | ||
317 | + recomputeLeastCommonAncestor(); | ||
318 | + } | ||
319 | + | ||
320 | + public DocumentPath leastCommonAncestorPath() { | ||
321 | + return leastCommonAncestorPath; | ||
322 | + } | ||
323 | + | ||
324 | + public <M> void publish(String topic, M message) { | ||
325 | + commits.stream().findAny().ifPresent(commit -> commit.session().publish(topic, message)); | ||
326 | + } | ||
327 | + | ||
328 | + public void close() { | ||
329 | + commits.forEach(Commit::close); | ||
330 | + commits.clear(); | ||
331 | + leastCommonAncestorPath = null; | ||
332 | + } | ||
333 | + | ||
334 | + private void recomputeLeastCommonAncestor() { | ||
335 | + this.leastCommonAncestorPath = DocumentPath.leastCommonAncestor(commits.stream() | ||
336 | + .map(c -> c.operation().path()) | ||
337 | + .collect(Collectors.toList())); | ||
293 | } | 338 | } |
294 | } | 339 | } |
295 | } | 340 | } | ... | ... |
... | @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; | ... | @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; |
22 | import static org.junit.Assert.assertNull; | 22 | import static org.junit.Assert.assertNull; |
23 | import static org.junit.Assert.assertTrue; | 23 | import static org.junit.Assert.assertTrue; |
24 | import static org.junit.Assert.fail; | 24 | import static org.junit.Assert.fail; |
25 | +import io.atomix.AtomixClient; | ||
25 | import io.atomix.resource.ResourceType; | 26 | import io.atomix.resource.ResourceType; |
26 | 27 | ||
27 | import java.util.Map; | 28 | import java.util.Map; |
... | @@ -343,12 +344,53 @@ public class AtomixDocumentTreeTest extends AtomixTestBase { | ... | @@ -343,12 +344,53 @@ public class AtomixDocumentTreeTest extends AtomixTestBase { |
343 | assertArrayEquals("xy".getBytes(), event.newValue().get().value()); | 344 | assertArrayEquals("xy".getBytes(), event.newValue().get().value()); |
344 | } | 345 | } |
345 | 346 | ||
347 | + @Test | ||
348 | + public void testFilteredNotifications() throws Throwable { | ||
349 | + AtomixClient client1 = createAtomixClient(); | ||
350 | + AtomixClient client2 = createAtomixClient(); | ||
351 | + | ||
352 | + String treeName = UUID.randomUUID().toString(); | ||
353 | + AtomixDocumentTree tree1 = client1.getResource(treeName, AtomixDocumentTree.class).join(); | ||
354 | + AtomixDocumentTree tree2 = client2.getResource(treeName, AtomixDocumentTree.class).join(); | ||
355 | + | ||
356 | + TestEventListener listener1a = new TestEventListener(3); | ||
357 | + TestEventListener listener1ab = new TestEventListener(2); | ||
358 | + TestEventListener listener2abc = new TestEventListener(1); | ||
359 | + | ||
360 | + tree1.addListener(DocumentPath.from("root.a"), listener1a).join(); | ||
361 | + tree1.addListener(DocumentPath.from("root.a.b"), listener1ab).join(); | ||
362 | + tree2.addListener(DocumentPath.from("root.a.b.c"), listener2abc).join(); | ||
363 | + | ||
364 | + tree1.createRecursive(DocumentPath.from("root.a.b.c"), "abc".getBytes()).join(); | ||
365 | + DocumentTreeEvent<byte[]> event = listener1a.event(); | ||
366 | + assertEquals(DocumentPath.from("root.a"), event.path()); | ||
367 | + event = listener1a.event(); | ||
368 | + assertEquals(DocumentPath.from("root.a.b"), event.path()); | ||
369 | + event = listener1a.event(); | ||
370 | + assertEquals(DocumentPath.from("root.a.b.c"), event.path()); | ||
371 | + event = listener1ab.event(); | ||
372 | + assertEquals(DocumentPath.from("root.a.b"), event.path()); | ||
373 | + event = listener1ab.event(); | ||
374 | + assertEquals(DocumentPath.from("root.a.b.c"), event.path()); | ||
375 | + event = listener2abc.event(); | ||
376 | + assertEquals(DocumentPath.from("root.a.b.c"), event.path()); | ||
377 | + } | ||
378 | + | ||
346 | private static class TestEventListener implements DocumentTreeListener<byte[]> { | 379 | private static class TestEventListener implements DocumentTreeListener<byte[]> { |
347 | 380 | ||
348 | - private final BlockingQueue<DocumentTreeEvent<byte[]>> queue = new ArrayBlockingQueue<>(1); | 381 | + private final BlockingQueue<DocumentTreeEvent<byte[]>> queue; |
382 | + | ||
383 | + public TestEventListener() { | ||
384 | + this(1); | ||
385 | + } | ||
386 | + | ||
387 | + public TestEventListener(int maxEvents) { | ||
388 | + queue = new ArrayBlockingQueue<>(maxEvents); | ||
389 | + } | ||
349 | 390 | ||
350 | @Override | 391 | @Override |
351 | public void event(DocumentTreeEvent<byte[]> event) { | 392 | public void event(DocumentTreeEvent<byte[]> event) { |
393 | + | ||
352 | try { | 394 | try { |
353 | queue.put(event); | 395 | queue.put(event); |
354 | } catch (InterruptedException e) { | 396 | } catch (InterruptedException e) { | ... | ... |
-
Please register or login to post a comment