Madan Jampani
Committed by Gerrit Code Review

Supporting Atomix classes for DocumentTree distributed primitive

Change-Id: I754222337401f90f976d4152b6abbdf2e1a4df8e
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 java.util.Map;
20 +import java.util.concurrent.CompletableFuture;
21 +
22 +import javax.annotation.concurrent.NotThreadSafe;
23 +
24 +/**
25 + * A hierarchical <a href="https://en.wikipedia.org/wiki/Document_Object_Model">document tree</a> data structure.
26 + *
27 + * @param <V> document tree value type
28 + */
29 +@NotThreadSafe
30 +public interface AsyncDocumentTree<V> extends DistributedPrimitive {
31 +
32 + /**
33 + * Returns the {@link DocumentPath path} to root of the tree.
34 + *
35 + * @return path to root of the tree
36 + */
37 + DocumentPath root();
38 +
39 + /**
40 + * Returns the child values for this node.
41 + *
42 + * @param path path to the node
43 + * @return future for mapping from a child name to its value
44 + * @throws NoSuchDocumentPathException if the path does not point to a valid node
45 + */
46 + CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path);
47 +
48 + /**
49 + * Returns a value (and version) of the tree node at specified path.
50 + *
51 + * @param path path to node
52 + * @return future for node value or {@code null} if path does not point to a valid node
53 + */
54 + CompletableFuture<Versioned<V>> get(DocumentPath path);
55 +
56 + /**
57 + * Creates or updates a document tree node.
58 + *
59 + * @param path path for the node to create or update
60 + * @param value the non-null value to be associated with the key
61 + * @return future for the previous mapping or {@code null} if there was no previous mapping. Future will
62 + * be completed with a NoSuchDocumentPathException if the parent node (for the node to create/update) does not exist
63 + */
64 + CompletableFuture<Versioned<V>> set(DocumentPath path, V value);
65 +
66 + /**
67 + * Creates a document tree node if one does not exist already.
68 + *
69 + * @param path path for the node to create
70 + * @param value the non-null value to be associated with the key
71 + * @return future that is completed with {@code true} if the mapping could be added
72 + * successfully; {@code false} otherwise. Future will be completed with a
73 + * IllegalDocumentModificationException if the parent node (for the node to create) does not exist
74 + */
75 + CompletableFuture<Boolean> create(DocumentPath path, V value);
76 +
77 + /**
78 + * Conditionally updates a tree node if the current version matches a specified version.
79 + *
80 + * @param path path for the node to create
81 + * @param newValue the non-null value to be associated with the key
82 + * @param version current version of the value for update to occur
83 + * @return future that is completed with {@code true} if the update was made and the tree was
84 + * modified, {@code false} otherwise.
85 + */
86 + CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version);
87 +
88 + /**
89 + * Conditionally updates a tree node if the current value matches a specified value.
90 + *
91 + * @param path path for the node to create
92 + * @param newValue the non-null value to be associated with the key
93 + * @param currentValue current value for update to occur
94 + * @return future that is completed with {@code true} if the update was made and the tree was
95 + * modified, {@code false} otherwise.
96 + */
97 + CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue);
98 +
99 + /**
100 + * Removes the node with the specified path.
101 + *
102 + * @param path path for the node to remove
103 + * @return future for the previous value. Future will be completed with a
104 + * 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
106 + * NoSuchDocumentPathException if the node to be removed does not exist
107 + */
108 + CompletableFuture<Versioned<V>> removeNode(DocumentPath path);
109 +
110 + /**
111 + * Registers a listener to be notified when a subtree rooted at the specified path
112 + * is modified.
113 + *
114 + * @param path path to root of subtree to monitor for updates
115 + * @param listener listener to be notified
116 + */
117 + CompletableFuture<Void> addListener(DocumentPath path, DocumentTreeListener<V> listener);
118 +
119 + /**
120 + * Unregisters a previously added listener.
121 + *
122 + * @param listener listener to unregister
123 + */
124 + CompletableFuture<Void> removeListener(DocumentTreeListener<V> listener);
125 +
126 + /**
127 + * Registers a listener to be notified when the tree is modified.
128 + *
129 + * @param listener listener to be notified
130 + */
131 + default CompletableFuture<Void> addListener(DocumentTreeListener<V> listener) {
132 + return addListener(root(), listener);
133 + }
134 +}
...@@ -72,6 +72,11 @@ public interface DistributedPrimitive { ...@@ -72,6 +72,11 @@ public interface DistributedPrimitive {
72 WORK_QUEUE, 72 WORK_QUEUE,
73 73
74 /** 74 /**
75 + * Document tree.
76 + */
77 + DOCUMENT_TREE,
78 +
79 + /**
75 * Distributed topic. 80 * Distributed topic.
76 */ 81 */
77 TOPIC, 82 TOPIC,
......
...@@ -52,6 +52,14 @@ public class DocumentTreeEvent<V> { ...@@ -52,6 +52,14 @@ public class DocumentTreeEvent<V> {
52 private final Optional<Versioned<V>> newValue; 52 private final Optional<Versioned<V>> newValue;
53 private final Optional<Versioned<V>> oldValue; 53 private final Optional<Versioned<V>> oldValue;
54 54
55 + @SuppressWarnings("unused")
56 + private DocumentTreeEvent() {
57 + this.path = null;
58 + this.type = null;
59 + this.newValue = null;
60 + this.oldValue = null;
61 + }
62 +
55 /** 63 /**
56 * Constructs a new {@code DocumentTreeEvent}. 64 * Constructs a new {@code DocumentTreeEvent}.
57 * 65 *
......
...@@ -15,12 +15,13 @@ ...@@ -15,12 +15,13 @@
15 */ 15 */
16 package org.onosproject.store.primitives.impl; 16 package org.onosproject.store.primitives.impl;
17 17
18 -import com.google.common.collect.ImmutableList;
19 -import com.google.common.collect.Maps;
20 import io.atomix.catalyst.serializer.Serializer; 18 import io.atomix.catalyst.serializer.Serializer;
21 import io.atomix.catalyst.serializer.TypeSerializerFactory; 19 import io.atomix.catalyst.serializer.TypeSerializerFactory;
22 import io.atomix.manager.util.ResourceManagerTypeResolver; 20 import io.atomix.manager.util.ResourceManagerTypeResolver;
23 import io.atomix.variables.internal.LongCommands; 21 import io.atomix.variables.internal.LongCommands;
22 +
23 +import java.util.Arrays;
24 +
24 import org.onlab.util.Match; 25 import org.onlab.util.Match;
25 import org.onosproject.cluster.Leader; 26 import org.onosproject.cluster.Leader;
26 import org.onosproject.cluster.Leadership; 27 import org.onosproject.cluster.Leadership;
...@@ -34,22 +35,28 @@ import org.onosproject.store.primitives.resources.impl.AtomixConsistentMultimapC ...@@ -34,22 +35,28 @@ import org.onosproject.store.primitives.resources.impl.AtomixConsistentMultimapC
34 import org.onosproject.store.primitives.resources.impl.AtomixConsistentSetMultimapFactory; 35 import org.onosproject.store.primitives.resources.impl.AtomixConsistentSetMultimapFactory;
35 import org.onosproject.store.primitives.resources.impl.AtomixConsistentTreeMapCommands; 36 import org.onosproject.store.primitives.resources.impl.AtomixConsistentTreeMapCommands;
36 import org.onosproject.store.primitives.resources.impl.AtomixConsistentTreeMapFactory; 37 import org.onosproject.store.primitives.resources.impl.AtomixConsistentTreeMapFactory;
38 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands;
39 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeFactory;
37 import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands; 40 import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands;
38 import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorFactory; 41 import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorFactory;
39 import org.onosproject.store.primitives.resources.impl.AtomixWorkQueueCommands; 42 import org.onosproject.store.primitives.resources.impl.AtomixWorkQueueCommands;
40 import org.onosproject.store.primitives.resources.impl.AtomixWorkQueueFactory; 43 import org.onosproject.store.primitives.resources.impl.AtomixWorkQueueFactory;
41 import org.onosproject.store.primitives.resources.impl.CommitResult; 44 import org.onosproject.store.primitives.resources.impl.CommitResult;
45 +import org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult;
42 import org.onosproject.store.primitives.resources.impl.MapEntryUpdateResult; 46 import org.onosproject.store.primitives.resources.impl.MapEntryUpdateResult;
43 import org.onosproject.store.primitives.resources.impl.PrepareResult; 47 import org.onosproject.store.primitives.resources.impl.PrepareResult;
44 import org.onosproject.store.primitives.resources.impl.RollbackResult; 48 import org.onosproject.store.primitives.resources.impl.RollbackResult;
45 import org.onosproject.store.serializers.KryoNamespaces; 49 import org.onosproject.store.serializers.KryoNamespaces;
50 +import org.onosproject.store.service.DocumentPath;
51 +import org.onosproject.store.service.DocumentTreeEvent;
46 import org.onosproject.store.service.MapEvent; 52 import org.onosproject.store.service.MapEvent;
47 import org.onosproject.store.service.MapTransaction; 53 import org.onosproject.store.service.MapTransaction;
48 import org.onosproject.store.service.Task; 54 import org.onosproject.store.service.Task;
49 import org.onosproject.store.service.Versioned; 55 import org.onosproject.store.service.Versioned;
50 import org.onosproject.store.service.WorkQueueStats; 56 import org.onosproject.store.service.WorkQueueStats;
51 57
52 -import java.util.Arrays; 58 +import com.google.common.collect.ImmutableList;
59 +import com.google.common.collect.Maps;
53 60
54 /** 61 /**
55 * Serializer utility for Atomix Catalyst. 62 * Serializer utility for Atomix Catalyst.
...@@ -69,6 +76,11 @@ public final class CatalystSerializers { ...@@ -69,6 +76,11 @@ public final class CatalystSerializers {
69 Transaction.State.class, 76 Transaction.State.class,
70 PrepareResult.class, 77 PrepareResult.class,
71 CommitResult.class, 78 CommitResult.class,
79 + DocumentPath.class,
80 + DocumentTreeUpdateResult.class,
81 + DocumentTreeUpdateResult.Status.class,
82 + DocumentTreeEvent.class,
83 + DocumentTreeEvent.Type.class,
72 RollbackResult.class)); 84 RollbackResult.class));
73 // ONOS classes 85 // ONOS classes
74 serializer.register(Change.class, factory); 86 serializer.register(Change.class, factory);
...@@ -90,6 +102,11 @@ public final class CatalystSerializers { ...@@ -90,6 +102,11 @@ public final class CatalystSerializers {
90 serializer.register(MapEvent.class, factory); 102 serializer.register(MapEvent.class, factory);
91 serializer.register(Task.class, factory); 103 serializer.register(Task.class, factory);
92 serializer.register(WorkQueueStats.class, factory); 104 serializer.register(WorkQueueStats.class, factory);
105 + serializer.register(DocumentPath.class, factory);
106 + serializer.register(DocumentTreeUpdateResult.class, factory);
107 + serializer.register(DocumentTreeUpdateResult.Status.class, factory);
108 + serializer.register(DocumentTreeEvent.class, factory);
109 + serializer.register(DocumentTreeEvent.Type.class, factory);
93 serializer.register(Maps.immutableEntry("a", "b").getClass(), factory); 110 serializer.register(Maps.immutableEntry("a", "b").getClass(), factory);
94 serializer.register(ImmutableList.of().getClass(), factory); 111 serializer.register(ImmutableList.of().getClass(), factory);
95 112
...@@ -97,6 +114,7 @@ public final class CatalystSerializers { ...@@ -97,6 +114,7 @@ public final class CatalystSerializers {
97 serializer.resolve(new AtomixConsistentMapCommands.TypeResolver()); 114 serializer.resolve(new AtomixConsistentMapCommands.TypeResolver());
98 serializer.resolve(new AtomixLeaderElectorCommands.TypeResolver()); 115 serializer.resolve(new AtomixLeaderElectorCommands.TypeResolver());
99 serializer.resolve(new AtomixWorkQueueCommands.TypeResolver()); 116 serializer.resolve(new AtomixWorkQueueCommands.TypeResolver());
117 + serializer.resolve(new AtomixDocumentTreeCommands.TypeResolver());
100 serializer.resolve(new ResourceManagerTypeResolver()); 118 serializer.resolve(new ResourceManagerTypeResolver());
101 serializer.resolve(new AtomixConsistentTreeMapCommands.TypeResolver()); 119 serializer.resolve(new AtomixConsistentTreeMapCommands.TypeResolver());
102 serializer.resolve(new AtomixConsistentMultimapCommands.TypeResolver()); 120 serializer.resolve(new AtomixConsistentMultimapCommands.TypeResolver());
...@@ -104,6 +122,7 @@ public final class CatalystSerializers { ...@@ -104,6 +122,7 @@ public final class CatalystSerializers {
104 serializer.registerClassLoader(AtomixConsistentMapFactory.class) 122 serializer.registerClassLoader(AtomixConsistentMapFactory.class)
105 .registerClassLoader(AtomixLeaderElectorFactory.class) 123 .registerClassLoader(AtomixLeaderElectorFactory.class)
106 .registerClassLoader(AtomixWorkQueueFactory.class) 124 .registerClassLoader(AtomixWorkQueueFactory.class)
125 + .registerClassLoader(AtomixDocumentTreeFactory.class)
107 .registerClassLoader(AtomixConsistentTreeMapFactory.class) 126 .registerClassLoader(AtomixConsistentTreeMapFactory.class)
108 .registerClassLoader(AtomixConsistentSetMultimapFactory.class); 127 .registerClassLoader(AtomixConsistentSetMultimapFactory.class);
109 128
......
...@@ -15,14 +15,15 @@ ...@@ -15,14 +15,15 @@
15 */ 15 */
16 package org.onosproject.store.primitives.resources.impl; 16 package org.onosproject.store.primitives.resources.impl;
17 17
18 +import static com.google.common.base.Preconditions.checkState;
18 import static org.onosproject.store.service.MapEvent.Type.INSERT; 19 import static org.onosproject.store.service.MapEvent.Type.INSERT;
19 import static org.onosproject.store.service.MapEvent.Type.REMOVE; 20 import static org.onosproject.store.service.MapEvent.Type.REMOVE;
20 import static org.onosproject.store.service.MapEvent.Type.UPDATE; 21 import static org.onosproject.store.service.MapEvent.Type.UPDATE;
21 import static org.slf4j.LoggerFactory.getLogger; 22 import static org.slf4j.LoggerFactory.getLogger;
22 -import io.atomix.copycat.server.session.ServerSession;
23 import io.atomix.copycat.server.Commit; 23 import io.atomix.copycat.server.Commit;
24 import io.atomix.copycat.server.Snapshottable; 24 import io.atomix.copycat.server.Snapshottable;
25 import io.atomix.copycat.server.StateMachineExecutor; 25 import io.atomix.copycat.server.StateMachineExecutor;
26 +import io.atomix.copycat.server.session.ServerSession;
26 import io.atomix.copycat.server.session.SessionListener; 27 import io.atomix.copycat.server.session.SessionListener;
27 import io.atomix.copycat.server.storage.snapshot.SnapshotReader; 28 import io.atomix.copycat.server.storage.snapshot.SnapshotReader;
28 import io.atomix.copycat.server.storage.snapshot.SnapshotWriter; 29 import io.atomix.copycat.server.storage.snapshot.SnapshotWriter;
...@@ -68,15 +69,13 @@ import com.google.common.collect.Lists; ...@@ -68,15 +69,13 @@ import com.google.common.collect.Lists;
68 import com.google.common.collect.Maps; 69 import com.google.common.collect.Maps;
69 import com.google.common.collect.Sets; 70 import com.google.common.collect.Sets;
70 71
71 -import static com.google.common.base.Preconditions.checkState;
72 -
73 /** 72 /**
74 * State Machine for {@link AtomixConsistentMap} resource. 73 * State Machine for {@link AtomixConsistentMap} resource.
75 */ 74 */
76 public class AtomixConsistentMapState extends ResourceStateMachine implements SessionListener, Snapshottable { 75 public class AtomixConsistentMapState extends ResourceStateMachine implements SessionListener, Snapshottable {
77 76
78 private final Logger log = getLogger(getClass()); 77 private final Logger log = getLogger(getClass());
79 - private final Map<Long, Commit<? extends AtomixConsistentMapCommands.Listen>> listeners = new HashMap<>(); 78 + private final Map<Long, Commit<? extends Listen>> listeners = new HashMap<>();
80 private final Map<String, MapEntryValue> mapEntries = new HashMap<>(); 79 private final Map<String, MapEntryValue> mapEntries = new HashMap<>();
81 private final Set<String> preparedKeys = Sets.newHashSet(); 80 private final Set<String> preparedKeys = Sets.newHashSet();
82 private final Map<TransactionId, Commit<? extends TransactionPrepare>> pendingTransactions = Maps.newHashMap(); 81 private final Map<TransactionId, Commit<? extends TransactionPrepare>> pendingTransactions = Maps.newHashMap();
......
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.primitives.resources.impl;
18 +
19 +import static com.google.common.base.Preconditions.checkNotNull;
20 +import io.atomix.copycat.client.CopycatClient;
21 +import io.atomix.resource.AbstractResource;
22 +import io.atomix.resource.ResourceTypeInfo;
23 +
24 +import java.util.HashMap;
25 +import java.util.List;
26 +import java.util.Map;
27 +import java.util.Properties;
28 +import java.util.concurrent.CompletableFuture;
29 +import java.util.concurrent.Executor;
30 +
31 +import org.onlab.util.Match;
32 +import org.onlab.util.Tools;
33 +import org.onosproject.store.primitives.resources.impl.AtomixConsistentMapCommands.Unlisten;
34 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Clear;
35 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Get;
36 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.GetChildren;
37 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Listen;
38 +import org.onosproject.store.primitives.resources.impl.AtomixDocumentTreeCommands.Update;
39 +import org.onosproject.store.service.AsyncDocumentTree;
40 +import org.onosproject.store.service.DocumentPath;
41 +import org.onosproject.store.service.DocumentTreeEvent;
42 +import org.onosproject.store.service.DocumentTreeListener;
43 +import org.onosproject.store.service.IllegalDocumentModificationException;
44 +import org.onosproject.store.service.NoSuchDocumentPathException;
45 +import org.onosproject.store.service.Versioned;
46 +
47 +import com.google.common.util.concurrent.MoreExecutors;
48 +
49 +/**
50 + * Distributed resource providing the {@link AsyncDocumentTree} primitive.
51 + */
52 +@ResourceTypeInfo(id = -156, factory = AtomixDocumentTreeFactory.class)
53 +public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree>
54 + implements AsyncDocumentTree<byte[]> {
55 +
56 + private final Map<DocumentTreeListener<byte[]>, Executor> eventListeners = new HashMap<>();
57 + public static final String CHANGE_SUBJECT = "changeEvents";
58 +
59 + protected AtomixDocumentTree(CopycatClient client, Properties options) {
60 + super(client, options);
61 + }
62 +
63 + @Override
64 + public CompletableFuture<AtomixDocumentTree> open() {
65 + return super.open().thenApply(result -> {
66 + client.onStateChange(state -> {
67 + if (state == CopycatClient.State.CONNECTED && isListening()) {
68 + client.submit(new Listen());
69 + }
70 + });
71 + client.onEvent(CHANGE_SUBJECT, this::processTreeUpdates);
72 + return result;
73 + });
74 + }
75 +
76 + @Override
77 + public String name() {
78 + return null;
79 + }
80 +
81 + @Override
82 + public Type primitiveType() {
83 + return Type.DOCUMENT_TREE;
84 + }
85 +
86 + @Override
87 + public CompletableFuture<Void> destroy() {
88 + return client.submit(new Clear());
89 + }
90 +
91 + @Override
92 + public DocumentPath root() {
93 + return DocumentPath.from("root");
94 + }
95 +
96 + @Override
97 + public CompletableFuture<Map<String, Versioned<byte[]>>> getChildren(DocumentPath path) {
98 + return client.submit(new GetChildren(checkNotNull(path)));
99 + }
100 +
101 + @Override
102 + public CompletableFuture<Versioned<byte[]>> get(DocumentPath path) {
103 + return client.submit(new Get(checkNotNull(path)));
104 + }
105 +
106 + @Override
107 + public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) {
108 + return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any()))
109 + .thenCompose(result -> {
110 + if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
111 + return Tools.exceptionalFuture(new NoSuchDocumentPathException());
112 + } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
113 + return Tools.exceptionalFuture(new IllegalDocumentModificationException());
114 + } else {
115 + return CompletableFuture.completedFuture(result);
116 + }
117 + }).thenApply(result -> result.oldValue());
118 + }
119 +
120 + @Override
121 + public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) {
122 + return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
123 + .thenCompose(result -> {
124 + if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
125 + return Tools.exceptionalFuture(new NoSuchDocumentPathException());
126 + } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
127 + return Tools.exceptionalFuture(new IllegalDocumentModificationException());
128 + } else {
129 + return CompletableFuture.completedFuture(result);
130 + }
131 + }).thenApply(result -> result.created());
132 + }
133 +
134 + @Override
135 + public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, long version) {
136 + return client.submit(new Update(checkNotNull(path), newValue, Match.any(), Match.ifValue(version)))
137 + .thenApply(result -> result.updated());
138 + }
139 +
140 + @Override
141 + public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) {
142 + return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any()))
143 + .thenCompose(result -> {
144 + if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
145 + return Tools.exceptionalFuture(new NoSuchDocumentPathException());
146 + } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
147 + return Tools.exceptionalFuture(new IllegalDocumentModificationException());
148 + } else {
149 + return CompletableFuture.completedFuture(result);
150 + }
151 + }).thenApply(result -> result.updated());
152 + }
153 +
154 + @Override
155 + public CompletableFuture<Versioned<byte[]>> removeNode(DocumentPath path) {
156 + if (path.equals(DocumentPath.from("root"))) {
157 + return Tools.exceptionalFuture(new IllegalDocumentModificationException());
158 + }
159 + return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any()))
160 + .thenCompose(result -> {
161 + if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
162 + return Tools.exceptionalFuture(new NoSuchDocumentPathException());
163 + } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
164 + return Tools.exceptionalFuture(new IllegalDocumentModificationException());
165 + } else {
166 + return CompletableFuture.completedFuture(result);
167 + }
168 + }).thenApply(result -> result.oldValue());
169 + }
170 +
171 + @Override
172 + public CompletableFuture<Void> addListener(DocumentPath path, DocumentTreeListener<byte[]> listener) {
173 + checkNotNull(path);
174 + checkNotNull(listener);
175 + // TODO: Support API that takes an executor
176 + if (isListening()) {
177 + eventListeners.putIfAbsent(listener, MoreExecutors.directExecutor());
178 + return CompletableFuture.completedFuture(null);
179 + } else {
180 + return client.submit(new Listen(path))
181 + .thenRun(() -> eventListeners.put(listener, MoreExecutors.directExecutor()));
182 + }
183 + }
184 +
185 + @Override
186 + public CompletableFuture<Void> removeListener(DocumentTreeListener<byte[]> listener) {
187 + checkNotNull(listener);
188 + if (eventListeners.remove(listener) != null && eventListeners.isEmpty()) {
189 + return client.submit(new Unlisten()).thenApply(v -> null);
190 + }
191 + return CompletableFuture.completedFuture(null);
192 + }
193 +
194 + private boolean isListening() {
195 + return !eventListeners.isEmpty();
196 + }
197 +
198 + private void processTreeUpdates(List<DocumentTreeEvent<byte[]>> events) {
199 + events.forEach(event ->
200 + eventListeners.forEach((listener, executor) -> executor.execute(() -> listener.event(event))));
201 + }
202 +}
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.primitives.resources.impl;
18 +
19 +import io.atomix.catalyst.buffer.BufferInput;
20 +import io.atomix.catalyst.buffer.BufferOutput;
21 +import io.atomix.catalyst.serializer.CatalystSerializable;
22 +import io.atomix.catalyst.serializer.SerializableTypeResolver;
23 +import io.atomix.catalyst.serializer.Serializer;
24 +import io.atomix.catalyst.serializer.SerializerRegistry;
25 +import io.atomix.copycat.Command;
26 +import io.atomix.copycat.Query;
27 +
28 +import java.util.Map;
29 +
30 +import org.onlab.util.Match;
31 +import org.onosproject.store.service.DocumentPath;
32 +import org.onosproject.store.service.Versioned;
33 +
34 +import com.google.common.base.MoreObjects;
35 +
36 +/**
37 + * {@link AtomixDocumentTree} resource state machine operations.
38 + */
39 +public class AtomixDocumentTreeCommands {
40 +
41 + /**
42 + * Abstract DocumentTree operation.
43 + */
44 + public abstract static class DocumentTreeOperation<V> implements CatalystSerializable {
45 +
46 + private DocumentPath path;
47 +
48 + DocumentTreeOperation(DocumentPath path) {
49 + this.path = path;
50 + }
51 +
52 + public DocumentPath path() {
53 + return path;
54 + }
55 +
56 + @Override
57 + public void writeObject(BufferOutput<?> buffer, Serializer serializer) {
58 + serializer.writeObject(path, buffer);
59 + }
60 +
61 + @Override
62 + public void readObject(BufferInput<?> buffer, Serializer serializer) {
63 + path = serializer.readObject(buffer);
64 + }
65 + }
66 +
67 + /**
68 + * Abstract DocumentTree query.
69 + */
70 + @SuppressWarnings("serial")
71 + public abstract static class DocumentTreeQuery<V> extends DocumentTreeOperation<V> implements Query<V> {
72 +
73 + DocumentTreeQuery(DocumentPath path) {
74 + super(path);
75 + }
76 +
77 + @Override
78 + public ConsistencyLevel consistency() {
79 + return ConsistencyLevel.SEQUENTIAL;
80 + }
81 + }
82 +
83 + /**
84 + * Abstract DocumentTree command.
85 + */
86 + @SuppressWarnings("serial")
87 + public abstract static class DocumentTreeCommand<V> extends DocumentTreeOperation<V> implements Command<V> {
88 +
89 + DocumentTreeCommand(DocumentPath path) {
90 + super(path);
91 + }
92 + }
93 +
94 + /**
95 + * DocumentTree#get query.
96 + */
97 + @SuppressWarnings("serial")
98 + public static class Get extends DocumentTreeQuery<Versioned<byte[]>> {
99 + public Get() {
100 + super(null);
101 + }
102 +
103 + public Get(DocumentPath path) {
104 + super(path);
105 + }
106 +
107 + @Override
108 + public String toString() {
109 + return MoreObjects.toStringHelper(getClass())
110 + .add("path", path())
111 + .toString();
112 + }
113 + }
114 +
115 + /**
116 + * DocumentTree#getChildren query.
117 + */
118 + @SuppressWarnings("serial")
119 + public static class GetChildren extends DocumentTreeQuery<Map<String, Versioned<byte[]>>> {
120 + public GetChildren() {
121 + super(null);
122 + }
123 +
124 + public GetChildren(DocumentPath path) {
125 + super(path);
126 + }
127 +
128 + @Override
129 + public String toString() {
130 + return MoreObjects.toStringHelper(getClass())
131 + .add("path", path())
132 + .toString();
133 + }
134 + }
135 +
136 + /**
137 + * DocumentTree update command.
138 + */
139 + @SuppressWarnings("serial")
140 + public static class Update extends DocumentTreeCommand<DocumentTreeUpdateResult<byte[]>> {
141 +
142 + private byte[] value;
143 + private Match<byte[]> valueMatch;
144 + private Match<Long> versionMatch;
145 +
146 + public Update() {
147 + super(null);
148 + this.value = null;
149 + this.valueMatch = null;
150 + this.versionMatch = null;
151 + }
152 +
153 + public Update(DocumentPath path, byte[] value, Match<byte[]> valueMatch, Match<Long> versionMatch) {
154 + super(path);
155 + this.value = value;
156 + this.valueMatch = valueMatch;
157 + this.versionMatch = versionMatch;
158 + }
159 +
160 + public byte[] value() {
161 + return value;
162 + }
163 +
164 + public Match<byte[]> valueMatch() {
165 + return valueMatch;
166 + }
167 +
168 + public Match<Long> versionMatch() {
169 + return versionMatch;
170 + }
171 +
172 + @Override
173 + public void writeObject(BufferOutput<?> buffer, Serializer serializer) {
174 + super.writeObject(buffer, serializer);
175 + serializer.writeObject(value, buffer);
176 + serializer.writeObject(valueMatch, buffer);
177 + serializer.writeObject(versionMatch, buffer);
178 + }
179 +
180 + @Override
181 + public void readObject(BufferInput<?> buffer, Serializer serializer) {
182 + super.readObject(buffer, serializer);
183 + value = serializer.readObject(buffer);
184 + valueMatch = serializer.readObject(buffer);
185 + versionMatch = serializer.readObject(buffer);
186 + }
187 +
188 + @Override
189 + public String toString() {
190 + return MoreObjects.toStringHelper(getClass())
191 + .add("path", path())
192 + .add("value", value)
193 + .add("valueMatch", valueMatch)
194 + .add("versionMatch", versionMatch)
195 + .toString();
196 + }
197 + }
198 +
199 + /**
200 + * Clear command.
201 + */
202 + @SuppressWarnings("serial")
203 + public static class Clear implements Command<Void>, CatalystSerializable {
204 + @Override
205 + public void writeObject(BufferOutput<?> buffer, Serializer serializer) {
206 + }
207 +
208 + @Override
209 + public void readObject(BufferInput<?> buffer, Serializer serializer) {
210 + }
211 + }
212 +
213 + /**
214 + * Change listen.
215 + */
216 + @SuppressWarnings("serial")
217 + public static class Listen extends DocumentTreeCommand<Void> {
218 +
219 + public Listen() {
220 + this(DocumentPath.from("root"));
221 + }
222 +
223 + public Listen(DocumentPath path) {
224 + super(path);
225 + }
226 +
227 + @Override
228 + public void writeObject(BufferOutput<?> buffer, Serializer serializer) {
229 + }
230 +
231 + @Override
232 + public void readObject(BufferInput<?> buffer, Serializer serializer) {
233 + }
234 + }
235 +
236 + /**
237 + * Change unlisten.
238 + */
239 + @SuppressWarnings("serial")
240 + public static class Unlisten extends DocumentTreeCommand<Void> {
241 +
242 + public Unlisten() {
243 + this(DocumentPath.from("root"));
244 + }
245 +
246 + public Unlisten(DocumentPath path) {
247 + super(path);
248 + }
249 +
250 + @Override
251 + public void writeObject(BufferOutput<?> buffer, Serializer serializer) {
252 + }
253 +
254 + @Override
255 + public void readObject(BufferInput<?> buffer, Serializer serializer) {
256 + }
257 + }
258 +
259 + /**
260 + * DocumentTree command type resolver.
261 + */
262 + public static class TypeResolver implements SerializableTypeResolver {
263 + @Override
264 + public void resolve(SerializerRegistry registry) {
265 + registry.register(Get.class, -911);
266 + registry.register(GetChildren.class, -912);
267 + registry.register(Update.class, -913);
268 + registry.register(Listen.class, -914);
269 + registry.register(Unlisten.class, -915);
270 + registry.register(Clear.class, -916);
271 + }
272 + }
273 +}
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 +package org.onosproject.store.primitives.resources.impl;
17 +
18 +import io.atomix.catalyst.serializer.SerializableTypeResolver;
19 +import io.atomix.copycat.client.CopycatClient;
20 +import io.atomix.resource.ResourceFactory;
21 +import io.atomix.resource.ResourceStateMachine;
22 +
23 +import java.util.Properties;
24 +
25 +/**
26 + * {@link AtomixDocumentTree} resource factory.
27 + *
28 + */
29 +public class AtomixDocumentTreeFactory implements ResourceFactory<AtomixDocumentTree> {
30 +
31 + @Override
32 + public SerializableTypeResolver createSerializableTypeResolver() {
33 + return new AtomixDocumentTreeCommands.TypeResolver();
34 + }
35 +
36 + @Override
37 + public ResourceStateMachine createStateMachine(Properties config) {
38 + return new AtomixDocumentTreeState(config);
39 + }
40 +
41 + @Override
42 + public AtomixDocumentTree createInstance(CopycatClient client, Properties options) {
43 + return new AtomixDocumentTree(client, options);
44 + }
45 + }
...\ No newline at end of file ...\ No newline at end of file
...@@ -19,7 +19,7 @@ package org.onosproject.store.primitives.resources.impl; ...@@ -19,7 +19,7 @@ package org.onosproject.store.primitives.resources.impl;
19 import java.util.Iterator; 19 import java.util.Iterator;
20 import java.util.Map; 20 import java.util.Map;
21 import java.util.Objects; 21 import java.util.Objects;
22 -import java.util.concurrent.atomic.AtomicInteger; 22 +import java.util.concurrent.atomic.AtomicLong;
23 23
24 import org.onosproject.store.service.DocumentPath; 24 import org.onosproject.store.service.DocumentPath;
25 import org.onosproject.store.service.DocumentTree; 25 import org.onosproject.store.service.DocumentTree;
...@@ -30,6 +30,7 @@ import org.onosproject.store.service.NoSuchDocumentPathException; ...@@ -30,6 +30,7 @@ import org.onosproject.store.service.NoSuchDocumentPathException;
30 import org.onosproject.store.service.Versioned; 30 import org.onosproject.store.service.Versioned;
31 31
32 import com.google.common.base.Preconditions; 32 import com.google.common.base.Preconditions;
33 +import com.google.common.base.Supplier;
33 import com.google.common.collect.Maps; 34 import com.google.common.collect.Maps;
34 35
35 /** 36 /**
...@@ -41,10 +42,17 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { ...@@ -41,10 +42,17 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
41 42
42 private static final DocumentPath ROOT_PATH = DocumentPath.from("root"); 43 private static final DocumentPath ROOT_PATH = DocumentPath.from("root");
43 private final DefaultDocumentTreeNode<V> root; 44 private final DefaultDocumentTreeNode<V> root;
44 - private final AtomicInteger versionCounter = new AtomicInteger(0); 45 + private final Supplier<Long> versionSupplier;
45 46
46 public DefaultDocumentTree() { 47 public DefaultDocumentTree() {
47 - root = new DefaultDocumentTreeNode<V>(ROOT_PATH, null, nextVersion(), null); 48 + AtomicLong versionCounter = new AtomicLong(0);
49 + versionSupplier = versionCounter::incrementAndGet;
50 + root = new DefaultDocumentTreeNode<V>(ROOT_PATH, null, versionSupplier.get(), null);
51 + }
52 +
53 + public DefaultDocumentTree(Supplier<Long> versionSupplier) {
54 + root = new DefaultDocumentTreeNode<V>(ROOT_PATH, null, versionSupplier.get(), null);
55 + this.versionSupplier = versionSupplier;
48 } 56 }
49 57
50 @Override 58 @Override
...@@ -74,7 +82,7 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { ...@@ -74,7 +82,7 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
74 checkRootModification(path); 82 checkRootModification(path);
75 DefaultDocumentTreeNode<V> node = getNode(path); 83 DefaultDocumentTreeNode<V> node = getNode(path);
76 if (node != null) { 84 if (node != null) {
77 - return node.update(value, nextVersion()); 85 + return node.update(value, versionSupplier.get());
78 } else { 86 } else {
79 create(path, value); 87 create(path, value);
80 return null; 88 return null;
...@@ -93,7 +101,7 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { ...@@ -93,7 +101,7 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
93 if (parentNode == null) { 101 if (parentNode == null) {
94 throw new IllegalDocumentModificationException(); 102 throw new IllegalDocumentModificationException();
95 } 103 }
96 - parentNode.addChild(simpleName(path), value, nextVersion()); 104 + parentNode.addChild(simpleName(path), value, versionSupplier.get());
97 return true; 105 return true;
98 } 106 }
99 107
...@@ -159,10 +167,6 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { ...@@ -159,10 +167,6 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> {
159 return currentNode; 167 return currentNode;
160 } 168 }
161 169
162 - private long nextVersion() {
163 - return versionCounter.incrementAndGet();
164 - }
165 -
166 private String simpleName(DocumentPath path) { 170 private String simpleName(DocumentPath path) {
167 return path.pathElements().get(path.pathElements().size() - 1); 171 return path.pathElements().get(path.pathElements().size() - 1);
168 } 172 }
......
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.primitives.resources.impl;
18 +
19 +import org.onosproject.store.service.DocumentPath;
20 +import org.onosproject.store.service.Versioned;
21 +
22 +import com.google.common.base.MoreObjects;
23 +
24 +/**
25 + * Result of a document tree node update operation.
26 + * <p>
27 + * Both old and new values are accessible along with a status of update.
28 + *
29 + * @param <V> value type
30 + */
31 +public class DocumentTreeUpdateResult<V> {
32 +
33 + public enum Status {
34 + /**
35 + * Indicates a successful update.
36 + */
37 + OK,
38 +
39 + /**
40 + * Indicates a noop i.e. existing and new value are both same.
41 + */
42 + NOOP,
43 +
44 + /**
45 + * Indicates a failed update due to a write lock.
46 + */
47 + WRITE_LOCK,
48 +
49 + /**
50 + * Indicates a failed update due to a invalid path.
51 + */
52 + INVALID_PATH,
53 +
54 + /**
55 + * Indicates a failed update due to a illegal modification attempt.
56 + */
57 + ILLEGAL_MODIFICATION,
58 + }
59 +
60 + private final DocumentPath path;
61 + private final Status status;
62 + private final Versioned<V> oldValue;
63 + private final Versioned<V> newValue;
64 +
65 + public DocumentTreeUpdateResult(DocumentPath path,
66 + Status status,
67 + Versioned<V> newValue,
68 + Versioned<V> oldValue) {
69 + this.status = status;
70 + this.path = path;
71 + this.newValue = newValue;
72 + this.oldValue = oldValue;
73 + }
74 +
75 + public static <V> DocumentTreeUpdateResult<V> invalidPath(DocumentPath path) {
76 + return new DocumentTreeUpdateResult<>(path, Status.INVALID_PATH, null, null);
77 + }
78 +
79 + public static <V> DocumentTreeUpdateResult<V> illegalModification(DocumentPath path) {
80 + return new DocumentTreeUpdateResult<>(path, Status.ILLEGAL_MODIFICATION, null, null);
81 + }
82 +
83 + public Status status() {
84 + return status;
85 + }
86 +
87 + public DocumentPath path() {
88 + return path;
89 + }
90 +
91 + public Versioned<V> oldValue() {
92 + return oldValue;
93 + }
94 +
95 + public Versioned<V> newValue() {
96 + return this.newValue;
97 + }
98 +
99 + public boolean updated() {
100 + return status == Status.OK;
101 + }
102 +
103 + public boolean created() {
104 + return updated() && oldValue == null;
105 + }
106 +
107 + @Override
108 + public String toString() {
109 + return MoreObjects.toStringHelper(getClass())
110 + .add("path", path)
111 + .add("status", status)
112 + .add("newValue", newValue)
113 + .add("oldValue", oldValue)
114 + .toString();
115 + }
116 +}