Jonathan Hart

Initial implementation of EventuallyConsistentMap.

The map uses the gossip schemes to replicate data between instances. It seems
to work for basic add and remove use cases right now, no anti-entropy yet.

ONOS-844.

Change-Id: I7d05a7b532e40c95ab14e2c8911f18514bd0a8ca
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +import org.onosproject.store.Timestamp;
19 +
20 +/**
21 + * Clock service that can generate timestamps per object.
22 + */
23 +public interface ClockService<T> {
24 +
25 + /**
26 + * Gets a new timestamp for the given object.
27 + *
28 + * @param object Object to get a timestamp for
29 + * @return the new timestamp
30 + */
31 + public Timestamp getTimestamp(T object);
32 +}
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +import java.util.Collection;
19 +import java.util.Map;
20 +import java.util.Set;
21 +
22 +/**
23 + * A distributed, eventually consistent map.
24 + *
25 + * This map does not offer read after writes consistency. Operations are
26 + * serialized via the timestamps issued by the clock service. If two updates
27 + * are in conflict, the update with the more recent timestamp will endure.
28 + *
29 + * The interface is mostly similar to {@link java.util.Map} with some minor
30 + * semantic changes and the addition of a listener framework (because the map
31 + * can be mutated by clients on other instances, not only through the local Java
32 + * API).
33 + *
34 + * Clients are expected to register an
35 + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if they
36 + * are interested in receiving notifications of update to the map.
37 + */
38 +public interface EventuallyConsistentMap<K, V> {
39 +
40 + /**
41 + * Returns the number of key-value mappings in this map.
42 + *
43 + * @return number of key-value mappings
44 + */
45 + public int size();
46 +
47 + /**
48 + * Returns true if this map is empty.
49 + *
50 + * @return true if this map is empty, otherwise false
51 + */
52 + public boolean isEmpty();
53 +
54 + /**
55 + * Returns true if the map contains a mapping for the specified key.
56 + *
57 + * @param key the key to check if this map contains
58 + * @return true if this map has a mapping for the key, otherwise false
59 + */
60 + public boolean containsKey(K key);
61 +
62 + /**
63 + * Returns true if the map contains a mapping from any key to the specified
64 + * value.
65 + *
66 + * @param value the value to check if this map has a mapping for
67 + * @return true if this map has a mapping to this value, otherwise false
68 + */
69 + public boolean containsValue(V value);
70 +
71 + /**
72 + * Returns the value mapped to the specified key.
73 + *
74 + * @param key the key to look up in this map
75 + * @return the value mapped to the key, or null if no mapping is found
76 + */
77 + public V get(K key);
78 +
79 + /**
80 + * Associates the specified value to the specified key in this map.
81 + * <p>
82 + * Note: this differs from the specification of {@link java.util.Map}
83 + * because it does not return the previous value associated with the key.
84 + * Clients are expected to register an
85 + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if
86 + * they are interested in receiving notification of updates to the map.
87 + * </p>
88 + *
89 + * @param key the key to add a mapping for in this map
90 + * @param value the value to associate with the key in this map
91 + */
92 + public void put(K key, V value);
93 +
94 + /**
95 + * Removes the mapping associated with the specified key from the map.
96 + * <p>
97 + * Note: this differs from the specification of {@link java.util.Map}
98 + * because it does not return the previous value associated with the key.
99 + * Clients are expected to register an
100 + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if
101 + * they are interested in receiving notification of updates to the map.
102 + * </p>
103 + *
104 + * @param key the key to remove the mapping for
105 + */
106 + public void remove(K key);
107 +
108 + /**
109 + * Adds mappings for all key-value pairs in the specified map to this map.
110 + * <p>
111 + * This will be more efficient in communication than calling individual put
112 + * operations.
113 + * </p>
114 + *
115 + * @param m a map of values to add to this map
116 + */
117 + public void putAll(Map<? extends K, ? extends V> m);
118 +
119 + /**
120 + * Removes all mappings from this map.
121 + */
122 + public void clear();
123 +
124 + /**
125 + * Returns a set of the keys in this map. Changes to the set are not
126 + * reflected back to the map.
127 + *
128 + * @return set of keys in the map
129 + */
130 + public Set<K> keySet();
131 +
132 + /**
133 + * Returns a collections of values in this map. Changes to the collection
134 + * are not reflected back to the map.
135 + *
136 + * @return collection of values in the map
137 + */
138 + public Collection<V> values();
139 +
140 + /**
141 + * Returns a set of mappings contained in this map. Changes to the set are
142 + * not reflected back to the map.
143 + *
144 + * @return set of key-value mappings in this map
145 + */
146 + public Set<Map.Entry<K, V>> entrySet();
147 +
148 + /**
149 + * Adds the specified listener to the map which will be notified whenever
150 + * the mappings in the map are changed.
151 + *
152 + * @param listener listener to register for events
153 + */
154 + public void addListener(EventuallyConsistentMapListener listener);
155 +
156 + /**
157 + * Removes the specified listener from the map such that it will no longer
158 + * receive change notifications.
159 + *
160 + * @param listener listener to deregister for events
161 + */
162 + public void removeListener(EventuallyConsistentMapListener listener);
163 +
164 + /**
165 + * Shuts down the map and breaks communication between different instances.
166 + * This allows the map objects to be cleaned up and garbage collected.
167 + * Calls to any methods on the map subsequent to calling destroy() will
168 + * throw a {@link java.lang.RuntimeException}.
169 + */
170 + public void destroy();
171 +}
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +/**
19 + * Event object signalling that the map was modified.
20 + */
21 +public class EventuallyConsistentMapEvent<K, V> {
22 +
23 + public enum Type {
24 + PUT,
25 + REMOVE
26 + }
27 +
28 + private final Type type;
29 + private final K key;
30 + private final V value;
31 +
32 + /**
33 + * Creates a new event object.
34 + *
35 + * @param type the type of the event
36 + * @param key the key the event concerns
37 + * @param value the value related to the key, or null for remove events
38 + */
39 + public EventuallyConsistentMapEvent(Type type, K key, V value) {
40 + this.type = type;
41 + this.key = key;
42 + this.value = value;
43 + }
44 +
45 + /**
46 + * Returns the type of the event.
47 + *
48 + * @return the type of the event
49 + */
50 + public Type type() {
51 + return type;
52 + }
53 +
54 + /**
55 + * Returns the key this event concerns.
56 + *
57 + * @return the key
58 + */
59 + public K key() {
60 + return key;
61 + }
62 +
63 + /**
64 + * Returns the value associated with this event.
65 + *
66 + * @return the value, or null if the event was REMOVE
67 + */
68 + public V value() {
69 + return value;
70 + }
71 +}
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +import com.google.common.base.MoreObjects;
19 +import org.onlab.util.KryoNamespace;
20 +import org.onosproject.cluster.ClusterService;
21 +import org.onosproject.cluster.NodeId;
22 +import org.onosproject.store.Timestamp;
23 +import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
24 +import org.onosproject.store.cluster.messaging.ClusterMessage;
25 +import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
26 +import org.onosproject.store.cluster.messaging.MessageSubject;
27 +import org.onosproject.store.serializers.KryoSerializer;
28 +import org.slf4j.Logger;
29 +import org.slf4j.LoggerFactory;
30 +
31 +import java.io.IOException;
32 +import java.util.ArrayList;
33 +import java.util.Collection;
34 +import java.util.Collections;
35 +import java.util.List;
36 +import java.util.Map;
37 +import java.util.Set;
38 +import java.util.concurrent.ConcurrentHashMap;
39 +import java.util.concurrent.CopyOnWriteArraySet;
40 +import java.util.concurrent.ExecutorService;
41 +import java.util.concurrent.Executors;
42 +import java.util.concurrent.ScheduledExecutorService;
43 +import java.util.stream.Collectors;
44 +
45 +import static com.google.common.base.Preconditions.checkNotNull;
46 +import static com.google.common.base.Preconditions.checkState;
47 +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
48 +import static org.onlab.util.Tools.minPriority;
49 +import static org.onlab.util.Tools.namedThreads;
50 +
51 +/**
52 + * Distributed Map implementation which uses optimistic replication and gossip
53 + * based techniques to provide an eventually consistent data store.
54 + */
55 +public class EventuallyConsistentMapImpl<K, V>
56 + implements EventuallyConsistentMap<K, V> {
57 +
58 + private static final Logger log = LoggerFactory.getLogger(EventuallyConsistentMapImpl.class);
59 +
60 + private final Map<K, Timestamped<V>> items;
61 + private final Map<K, Timestamp> removedItems;
62 +
63 + private final String mapName;
64 + private final ClusterService clusterService;
65 + private final ClusterCommunicationService clusterCommunicator;
66 + private final KryoSerializer serializer;
67 +
68 + private final ClockService<K> clockService;
69 +
70 + private final MessageSubject updateMessageSubject;
71 + private final MessageSubject removeMessageSubject;
72 +
73 + private final Set<EventuallyConsistentMapListener> listeners
74 + = new CopyOnWriteArraySet<>();
75 +
76 + private final ExecutorService executor;
77 +
78 + private final ScheduledExecutorService backgroundExecutor;
79 +
80 + private volatile boolean destroyed = false;
81 + private static final String ERROR_DESTROYED = " is already destroyed";
82 +
83 + // TODO: Make these anti-entropy params configurable
84 + private long initialDelaySec = 5;
85 + private long periodSec = 5;
86 +
87 + /**
88 + * Creates a new eventually consistent map shared amongst multiple instances.
89 + *
90 + * Each map is identified by a string map name. EventuallyConsistentMapImpl
91 + * objects in different JVMs that use the same map name will form a
92 + * distributed map across JVMs (provided the cluster service is aware of
93 + * both nodes).
94 + *
95 + * The client is expected to provide an
96 + * {@link org.onlab.util.KryoNamespace.Builder} with which all classes that
97 + * will be stored in this map have been registered (including referenced
98 + * classes). This serializer will be used to serialize both K and V for
99 + * inter-node notifications.
100 + *
101 + * The client must provide an {@link org.onosproject.store.impl.ClockService}
102 + * which can generate timestamps for a given key. The clock service is free
103 + * to generate timestamps however it wishes, however these timestamps will
104 + * be used to serialize updates to the map so they must be strict enough
105 + * to ensure updates are properly ordered for the use case (i.e. in some
106 + * cases wallclock time will suffice, whereas in other cases logical time
107 + * will be necessary).
108 + *
109 + * @param mapName a String identifier for the map.
110 + * @param clusterService the cluster service
111 + * @param clusterCommunicator the cluster communications service
112 + * @param serializerBuilder a Kryo namespace builder that can serialize
113 + * both K and V
114 + * @param clockService a clock service able to generate timestamps
115 + * for K
116 + */
117 + public EventuallyConsistentMapImpl(String mapName,
118 + ClusterService clusterService,
119 + ClusterCommunicationService clusterCommunicator,
120 + KryoNamespace.Builder serializerBuilder,
121 + ClockService<K> clockService) {
122 +
123 + this.mapName = checkNotNull(mapName);
124 + this.clusterService = checkNotNull(clusterService);
125 + this.clusterCommunicator = checkNotNull(clusterCommunicator);
126 +
127 + serializer = createSerializer(checkNotNull(serializerBuilder));
128 +
129 + this.clockService = checkNotNull(clockService);
130 +
131 + items = new ConcurrentHashMap<>();
132 + removedItems = new ConcurrentHashMap<>();
133 +
134 + executor = Executors
135 + .newCachedThreadPool(namedThreads("onos-ecm-" + mapName + "-fg-%d"));
136 +
137 + backgroundExecutor =
138 + newSingleThreadScheduledExecutor(minPriority(
139 + namedThreads("onos-ecm-" + mapName + "-bg-%d")));
140 +
141 + updateMessageSubject = new MessageSubject("ecm-" + mapName + "-update");
142 + clusterCommunicator.addSubscriber(updateMessageSubject,
143 + new InternalPutEventListener());
144 + removeMessageSubject = new MessageSubject("ecm-" + mapName + "-remove");
145 + clusterCommunicator.addSubscriber(removeMessageSubject,
146 + new InternalRemoveEventListener());
147 + }
148 +
149 + private KryoSerializer createSerializer(KryoNamespace.Builder builder) {
150 + return new KryoSerializer() {
151 + @Override
152 + protected void setupKryoPool() {
153 + // Add the map's internal helper classes to the user-supplied serializer
154 + serializerPool = builder
155 + .register(WallClockTimestamp.class)
156 + .register(PutEntry.class)
157 + .register(ArrayList.class)
158 + .register(InternalPutEvent.class)
159 + .register(InternalRemoveEvent.class)
160 + .build();
161 +
162 + // TODO anti-entropy classes
163 + }
164 + };
165 + }
166 +
167 + @Override
168 + public int size() {
169 + checkState(destroyed, mapName + ERROR_DESTROYED);
170 + return items.size();
171 + }
172 +
173 + @Override
174 + public boolean isEmpty() {
175 + checkState(destroyed, mapName + ERROR_DESTROYED);
176 + return items.isEmpty();
177 + }
178 +
179 + @Override
180 + public boolean containsKey(K key) {
181 + checkState(destroyed, mapName + ERROR_DESTROYED);
182 + return items.containsKey(key);
183 + }
184 +
185 + @Override
186 + public boolean containsValue(V value) {
187 + checkState(destroyed, mapName + ERROR_DESTROYED);
188 +
189 + return items.values().stream()
190 + .anyMatch(timestamped -> timestamped.value().equals(value));
191 + }
192 +
193 + @Override
194 + public V get(K key) {
195 + checkState(destroyed, mapName + ERROR_DESTROYED);
196 +
197 + Timestamped<V> value = items.get(key);
198 + if (value != null) {
199 + return value.value();
200 + }
201 + return null;
202 + }
203 +
204 + @Override
205 + public void put(K key, V value) {
206 + checkState(destroyed, mapName + ERROR_DESTROYED);
207 +
208 + Timestamp timestamp = clockService.getTimestamp(key);
209 + if (putInternal(key, value, timestamp)) {
210 + notifyPeers(new InternalPutEvent<>(key, value, timestamp));
211 + EventuallyConsistentMapEvent<K, V> externalEvent
212 + = new EventuallyConsistentMapEvent<>(
213 + EventuallyConsistentMapEvent.Type.PUT, key, value);
214 + notifyListeners(externalEvent);
215 + }
216 + }
217 +
218 + private boolean putInternal(K key, V value, Timestamp timestamp) {
219 + synchronized (this) {
220 + Timestamp removed = removedItems.get(key);
221 + if (removed != null && removed.compareTo(timestamp) > 0) {
222 + return false;
223 + }
224 +
225 + Timestamped<V> existing = items.get(key);
226 + if (existing != null && existing.isNewer(timestamp)) {
227 + return false;
228 + } else {
229 + items.put(key, new Timestamped<>(value, timestamp));
230 + removedItems.remove(key);
231 + return true;
232 + }
233 + }
234 + }
235 +
236 + @Override
237 + public void remove(K key) {
238 + checkState(destroyed, mapName + ERROR_DESTROYED);
239 +
240 + Timestamp timestamp = clockService.getTimestamp(key);
241 + if (removeInternal(key, timestamp)) {
242 + notifyPeers(new InternalRemoveEvent<>(key, timestamp));
243 + EventuallyConsistentMapEvent<K, V> externalEvent
244 + = new EventuallyConsistentMapEvent<>(
245 + EventuallyConsistentMapEvent.Type.REMOVE, key, null);
246 + notifyListeners(externalEvent);
247 + }
248 + }
249 +
250 + private boolean removeInternal(K key, Timestamp timestamp) {
251 + synchronized (this) {
252 + if (items.get(key) != null && items.get(key).isNewer(timestamp)) {
253 + return false;
254 + }
255 +
256 + items.remove(key);
257 + removedItems.put(key, timestamp);
258 + return true;
259 + }
260 + }
261 +
262 + @Override
263 + public void putAll(Map<? extends K, ? extends V> m) {
264 + checkState(destroyed, mapName + ERROR_DESTROYED);
265 +
266 + List<PutEntry<K, V>> updates = new ArrayList<>(m.size());
267 +
268 + for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
269 + K key = entry.getKey();
270 + V value = entry.getValue();
271 + Timestamp timestamp = clockService.getTimestamp(entry.getKey());
272 +
273 + if (putInternal(key, value, timestamp)) {
274 + updates.add(new PutEntry<>(key, value, timestamp));
275 + }
276 + }
277 +
278 + notifyPeers(new InternalPutEvent<>(updates));
279 +
280 + for (PutEntry<K, V> entry : updates) {
281 + EventuallyConsistentMapEvent<K, V> externalEvent =
282 + new EventuallyConsistentMapEvent<>(
283 + EventuallyConsistentMapEvent.Type.PUT, entry.key(), entry.value());
284 + notifyListeners(externalEvent);
285 + }
286 + }
287 +
288 + @Override
289 + public void clear() {
290 + checkState(destroyed, mapName + ERROR_DESTROYED);
291 +
292 + List<RemoveEntry<K>> removed = new ArrayList<>(items.size());
293 +
294 + for (K key : items.keySet()) {
295 + Timestamp timestamp = clockService.getTimestamp(key);
296 +
297 + if (removeInternal(key, timestamp)) {
298 + removed.add(new RemoveEntry<>(key, timestamp));
299 + }
300 + }
301 +
302 + notifyPeers(new InternalRemoveEvent<>(removed));
303 +
304 + for (RemoveEntry<K> entry : removed) {
305 + EventuallyConsistentMapEvent<K, V> externalEvent =
306 + new EventuallyConsistentMapEvent<>(
307 + EventuallyConsistentMapEvent.Type.REMOVE, entry.key(), null);
308 + notifyListeners(externalEvent);
309 + }
310 + }
311 +
312 + @Override
313 + public Set<K> keySet() {
314 + checkState(destroyed, mapName + ERROR_DESTROYED);
315 +
316 + return items.keySet();
317 + }
318 +
319 + @Override
320 + public Collection<V> values() {
321 + checkState(destroyed, mapName + ERROR_DESTROYED);
322 +
323 + return items.values().stream()
324 + .map(Timestamped::value)
325 + .collect(Collectors.toList());
326 + }
327 +
328 + @Override
329 + public Set<Map.Entry<K, V>> entrySet() {
330 + checkState(destroyed, mapName + ERROR_DESTROYED);
331 +
332 + return items.entrySet().stream()
333 + .map(e -> new Entry(e.getKey(), e.getValue().value()))
334 + .collect(Collectors.toSet());
335 + }
336 +
337 + @Override
338 + public void addListener(EventuallyConsistentMapListener listener) {
339 + checkState(destroyed, mapName + ERROR_DESTROYED);
340 +
341 + listeners.add(checkNotNull(listener));
342 + }
343 +
344 + @Override
345 + public void removeListener(EventuallyConsistentMapListener listener) {
346 + checkState(destroyed, mapName + ERROR_DESTROYED);
347 +
348 + listeners.remove(checkNotNull(listener));
349 + }
350 +
351 + @Override
352 + public void destroy() {
353 + destroyed = true;
354 +
355 + executor.shutdown();
356 + backgroundExecutor.shutdown();
357 +
358 + clusterCommunicator.removeSubscriber(updateMessageSubject);
359 + clusterCommunicator.removeSubscriber(removeMessageSubject);
360 + }
361 +
362 + private void notifyListeners(EventuallyConsistentMapEvent event) {
363 + for (EventuallyConsistentMapListener listener : listeners) {
364 + listener.event(event);
365 + }
366 + }
367 +
368 + private void notifyPeers(InternalPutEvent event) {
369 + try {
370 + log.debug("sending put {}", event);
371 + broadcastMessage(updateMessageSubject, event);
372 + } catch (IOException e) {
373 + // TODO this won't happen; remove from API
374 + log.debug("IOException broadcasting update", e);
375 + }
376 + }
377 +
378 + private void notifyPeers(InternalRemoveEvent event) {
379 + try {
380 + broadcastMessage(removeMessageSubject, event);
381 + } catch (IOException e) {
382 + // TODO this won't happen; remove from API
383 + log.debug("IOException broadcasting update", e);
384 + }
385 + }
386 +
387 + private void broadcastMessage(MessageSubject subject, Object event) throws
388 + IOException {
389 + ClusterMessage message = new ClusterMessage(
390 + clusterService.getLocalNode().id(),
391 + subject,
392 + serializer.encode(event));
393 + clusterCommunicator.broadcast(message);
394 + }
395 +
396 + private void unicastMessage(NodeId peer,
397 + MessageSubject subject,
398 + Object event) throws IOException {
399 + ClusterMessage message = new ClusterMessage(
400 + clusterService.getLocalNode().id(),
401 + subject,
402 + serializer.encode(event));
403 + clusterCommunicator.unicast(message, peer);
404 + }
405 +
406 + private final class Entry implements Map.Entry<K, V> {
407 +
408 + private final K key;
409 + private final V value;
410 +
411 + public Entry(K key, V value) {
412 + this.key = key;
413 + this.value = value;
414 + }
415 +
416 + @Override
417 + public K getKey() {
418 + return key;
419 + }
420 +
421 + @Override
422 + public V getValue() {
423 + return value;
424 + }
425 +
426 + @Override
427 + public V setValue(V value) {
428 + throw new UnsupportedOperationException();
429 + }
430 + }
431 +
432 + private final class InternalPutEventListener implements
433 + ClusterMessageHandler {
434 + @Override
435 + public void handle(ClusterMessage message) {
436 + log.debug("Received put event from peer: {}", message.sender());
437 + InternalPutEvent<K, V> event = serializer.decode(message.payload());
438 +
439 + executor.submit(() -> {
440 + try {
441 + for (PutEntry<K, V> entry : event.entries()) {
442 + K key = entry.key();
443 + V value = entry.value();
444 + Timestamp timestamp = entry.timestamp();
445 +
446 + if (putInternal(key, value, timestamp)) {
447 + EventuallyConsistentMapEvent externalEvent =
448 + new EventuallyConsistentMapEvent<>(
449 + EventuallyConsistentMapEvent.Type.PUT, key,
450 + value);
451 + notifyListeners(externalEvent);
452 + }
453 + }
454 + } catch (Exception e) {
455 + log.warn("Exception thrown handling put", e);
456 + }
457 + });
458 + }
459 + }
460 +
461 + private final class InternalRemoveEventListener implements
462 + ClusterMessageHandler {
463 + @Override
464 + public void handle(ClusterMessage message) {
465 + log.debug("Received remove event from peer: {}", message.sender());
466 + InternalRemoveEvent<K> event = serializer.decode(message.payload());
467 +
468 + executor.submit(() -> {
469 + try {
470 + for (RemoveEntry<K> entry : event.entries()) {
471 + K key = entry.key();
472 + Timestamp timestamp = entry.timestamp();
473 +
474 + if (removeInternal(key, timestamp)) {
475 + EventuallyConsistentMapEvent externalEvent = new EventuallyConsistentMapEvent<K, V>(
476 + EventuallyConsistentMapEvent.Type.REMOVE,
477 + key, null);
478 + notifyListeners(externalEvent);
479 + }
480 + }
481 + } catch (Exception e) {
482 + log.warn("Exception thrown handling remove", e);
483 + }
484 + });
485 + }
486 + }
487 +
488 + private static final class InternalPutEvent<K, V> {
489 + private final List<PutEntry<K, V>> entries;
490 +
491 + public InternalPutEvent(K key, V value, Timestamp timestamp) {
492 + entries = Collections
493 + .singletonList(new PutEntry<>(key, value, timestamp));
494 + }
495 +
496 + public InternalPutEvent(List<PutEntry<K, V>> entries) {
497 + this.entries = checkNotNull(entries);
498 + }
499 +
500 + // Needed for serialization.
501 + @SuppressWarnings("unused")
502 + private InternalPutEvent() {
503 + entries = null;
504 + }
505 +
506 + public List<PutEntry<K, V>> entries() {
507 + return entries;
508 + }
509 + }
510 +
511 + private static final class PutEntry<K, V> {
512 + private final K key;
513 + private final V value;
514 + private final Timestamp timestamp;
515 +
516 + public PutEntry(K key, V value, Timestamp timestamp) {
517 + this.key = checkNotNull(key);
518 + this.value = checkNotNull(value);
519 + this.timestamp = checkNotNull(timestamp);
520 + }
521 +
522 + // Needed for serialization.
523 + @SuppressWarnings("unused")
524 + private PutEntry() {
525 + this.key = null;
526 + this.value = null;
527 + this.timestamp = null;
528 + }
529 +
530 + public K key() {
531 + return key;
532 + }
533 +
534 + public V value() {
535 + return value;
536 + }
537 +
538 + public Timestamp timestamp() {
539 + return timestamp;
540 + }
541 +
542 + public String toString() {
543 + return MoreObjects.toStringHelper(getClass())
544 + .add("key", key)
545 + .add("value", value)
546 + .add("timestamp", timestamp)
547 + .toString();
548 + }
549 + }
550 +
551 + private static final class InternalRemoveEvent<K> {
552 + private final List<RemoveEntry<K>> entries;
553 +
554 + public InternalRemoveEvent(K key, Timestamp timestamp) {
555 + entries = Collections.singletonList(
556 + new RemoveEntry<>(key, timestamp));
557 + }
558 +
559 + public InternalRemoveEvent(List<RemoveEntry<K>> entries) {
560 + this.entries = checkNotNull(entries);
561 + }
562 +
563 + // Needed for serialization.
564 + @SuppressWarnings("unused")
565 + private InternalRemoveEvent() {
566 + entries = null;
567 + }
568 +
569 + public List<RemoveEntry<K>> entries() {
570 + return entries;
571 + }
572 + }
573 +
574 + private static final class RemoveEntry<K> {
575 + private final K key;
576 + private final Timestamp timestamp;
577 +
578 + public RemoveEntry(K key, Timestamp timestamp) {
579 + this.key = checkNotNull(key);
580 + this.timestamp = checkNotNull(timestamp);
581 + }
582 +
583 + // Needed for serialization.
584 + @SuppressWarnings("unused")
585 + private RemoveEntry() {
586 + this.key = null;
587 + this.timestamp = null;
588 + }
589 +
590 + public K key() {
591 + return key;
592 + }
593 +
594 + public Timestamp timestamp() {
595 + return timestamp;
596 + }
597 + }
598 +}
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +/**
19 + * Listener interested in receiving modification events for an
20 + * EventuallyConsistentMap.
21 + */
22 +public interface EventuallyConsistentMapListener {
23 +
24 + /**
25 + * Reacts to the specified event.
26 + *
27 + * @param event the event
28 + */
29 + public void event(EventuallyConsistentMapEvent event);
30 +}
1 +/*
2 + * Copyright 2015 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.impl;
17 +
18 +import org.onosproject.store.Timestamp;
19 +
20 +/**
21 + * A clock service which hands out wallclock-based timestamps.
22 + */
23 +public class WallclockClockManager<T> implements ClockService<T> {
24 + @Override
25 + public Timestamp getTimestamp(T object) {
26 + return new WallClockTimestamp();
27 + }
28 +}