Committed by
Yuta HIGUCHI
ONOS-3472 Fixing ConsistentMap key equality
- ConsistentMap's key equality is based on serialized byte[]. 2 Problems fixed by this patch: (1) By caching Key -> String representation, Cache will use Key's Object#equals for look up, which can possibly have different equality than byte[] equality, leading to wrong String to be used as a key in backend Database. Fixed by reversing the mapping. (2) Similar issues with keySet(), entrySet() Set based on reference equality needs to be used to avoid deduplication based on Object#equals Fixed by replacing Set implementation with MappingSet. Change-Id: I1b727abd2614a9b72b5b1d02ecca2de26493adcc
Showing
3 changed files
with
533 additions
and
16 deletions
... | @@ -20,6 +20,7 @@ import com.google.common.cache.CacheBuilder; | ... | @@ -20,6 +20,7 @@ import com.google.common.cache.CacheBuilder; |
20 | import com.google.common.cache.CacheLoader; | 20 | import com.google.common.cache.CacheLoader; |
21 | import com.google.common.cache.LoadingCache; | 21 | import com.google.common.cache.LoadingCache; |
22 | import com.google.common.collect.Maps; | 22 | import com.google.common.collect.Maps; |
23 | + | ||
23 | import org.onlab.util.HexString; | 24 | import org.onlab.util.HexString; |
24 | import org.onlab.util.SharedExecutors; | 25 | import org.onlab.util.SharedExecutors; |
25 | import org.onlab.util.Tools; | 26 | import org.onlab.util.Tools; |
... | @@ -33,6 +34,7 @@ import org.onosproject.store.service.Versioned; | ... | @@ -33,6 +34,7 @@ import org.onosproject.store.service.Versioned; |
33 | import org.slf4j.Logger; | 34 | import org.slf4j.Logger; |
34 | 35 | ||
35 | import java.util.Collection; | 36 | import java.util.Collection; |
37 | +import java.util.Collections; | ||
36 | import java.util.Map; | 38 | import java.util.Map; |
37 | import java.util.Map.Entry; | 39 | import java.util.Map.Entry; |
38 | import java.util.Objects; | 40 | import java.util.Objects; |
... | @@ -92,18 +94,25 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -92,18 +94,25 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
92 | private static final String ERROR_NULL_KEY = "Key cannot be null"; | 94 | private static final String ERROR_NULL_KEY = "Key cannot be null"; |
93 | private static final String ERROR_NULL_VALUE = "Null values are not allowed"; | 95 | private static final String ERROR_NULL_VALUE = "Null values are not allowed"; |
94 | 96 | ||
95 | - private final LoadingCache<K, String> keyCache = CacheBuilder.newBuilder() | 97 | + // String representation of serialized byte[] -> original key Object |
98 | + private final LoadingCache<String, K> keyCache = CacheBuilder.newBuilder() | ||
96 | .softValues() | 99 | .softValues() |
97 | - .build(new CacheLoader<K, String>() { | 100 | + .build(new CacheLoader<String, K>() { |
98 | 101 | ||
99 | @Override | 102 | @Override |
100 | - public String load(K key) { | 103 | + public K load(String key) { |
101 | - return HexString.toHexString(serializer.encode(key)); | 104 | + return serializer.decode(HexString.fromHexString(key)); |
102 | } | 105 | } |
103 | }); | 106 | }); |
104 | 107 | ||
108 | + protected String sK(K key) { | ||
109 | + String s = HexString.toHexString(serializer.encode(key)); | ||
110 | + keyCache.put(s, key); | ||
111 | + return s; | ||
112 | + } | ||
113 | + | ||
105 | protected K dK(String key) { | 114 | protected K dK(String key) { |
106 | - return serializer.decode(HexString.fromHexString(key)); | 115 | + return keyCache.getUnchecked(key); |
107 | } | 116 | } |
108 | 117 | ||
109 | public DefaultAsyncConsistentMap(String name, | 118 | public DefaultAsyncConsistentMap(String name, |
... | @@ -207,7 +216,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -207,7 +216,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
207 | public CompletableFuture<Boolean> containsKey(K key) { | 216 | public CompletableFuture<Boolean> containsKey(K key) { |
208 | checkNotNull(key, ERROR_NULL_KEY); | 217 | checkNotNull(key, ERROR_NULL_KEY); |
209 | final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_KEY); | 218 | final MeteringAgent.Context timer = monitor.startTimer(CONTAINS_KEY); |
210 | - return database.mapContainsKey(name, keyCache.getUnchecked(key)) | 219 | + return database.mapContainsKey(name, sK(key)) |
211 | .whenComplete((r, e) -> timer.stop(e)); | 220 | .whenComplete((r, e) -> timer.stop(e)); |
212 | } | 221 | } |
213 | 222 | ||
... | @@ -223,7 +232,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -223,7 +232,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
223 | public CompletableFuture<Versioned<V>> get(K key) { | 232 | public CompletableFuture<Versioned<V>> get(K key) { |
224 | checkNotNull(key, ERROR_NULL_KEY); | 233 | checkNotNull(key, ERROR_NULL_KEY); |
225 | final MeteringAgent.Context timer = monitor.startTimer(GET); | 234 | final MeteringAgent.Context timer = monitor.startTimer(GET); |
226 | - return database.mapGet(name, keyCache.getUnchecked(key)) | 235 | + return database.mapGet(name, sK(key)) |
227 | .whenComplete((r, e) -> timer.stop(e)) | 236 | .whenComplete((r, e) -> timer.stop(e)) |
228 | .thenApply(v -> v != null ? v.map(serializer::decode) : null); | 237 | .thenApply(v -> v != null ? v.map(serializer::decode) : null); |
229 | } | 238 | } |
... | @@ -328,10 +337,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -328,10 +337,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
328 | public CompletableFuture<Set<K>> keySet() { | 337 | public CompletableFuture<Set<K>> keySet() { |
329 | final MeteringAgent.Context timer = monitor.startTimer(KEY_SET); | 338 | final MeteringAgent.Context timer = monitor.startTimer(KEY_SET); |
330 | return database.mapKeySet(name) | 339 | return database.mapKeySet(name) |
331 | - .thenApply(s -> s | 340 | + .thenApply(s -> newMappingKeySet(s)) |
332 | - .stream() | ||
333 | - .map(this::dK) | ||
334 | - .collect(Collectors.toSet())) | ||
335 | .whenComplete((r, e) -> timer.stop(e)); | 341 | .whenComplete((r, e) -> timer.stop(e)); |
336 | } | 342 | } |
337 | 343 | ||
... | @@ -351,10 +357,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -351,10 +357,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
351 | final MeteringAgent.Context timer = monitor.startTimer(ENTRY_SET); | 357 | final MeteringAgent.Context timer = monitor.startTimer(ENTRY_SET); |
352 | return database.mapEntrySet(name) | 358 | return database.mapEntrySet(name) |
353 | .whenComplete((r, e) -> timer.stop(e)) | 359 | .whenComplete((r, e) -> timer.stop(e)) |
354 | - .thenApply(s -> s | 360 | + .thenApply(s -> newMappingEntrySet(s)); |
355 | - .stream() | ||
356 | - .map(this::mapRawEntry) | ||
357 | - .collect(Collectors.toSet())); | ||
358 | } | 361 | } |
359 | 362 | ||
360 | @Override | 363 | @Override |
... | @@ -413,17 +416,31 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V | ... | @@ -413,17 +416,31 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V |
413 | checkIfUnmodifiable(); | 416 | checkIfUnmodifiable(); |
414 | } | 417 | } |
415 | 418 | ||
419 | + private Set<K> newMappingKeySet(Set<String> s) { | ||
420 | + return new MappingSet<>(s, Collections::unmodifiableSet, | ||
421 | + this::sK, this::dK); | ||
422 | + } | ||
423 | + | ||
424 | + private Set<Entry<K, Versioned<V>>> newMappingEntrySet(Set<Entry<String, Versioned<byte[]>>> s) { | ||
425 | + return new MappingSet<>(s, Collections::unmodifiableSet, | ||
426 | + this::reverseMapRawEntry, this::mapRawEntry); | ||
427 | + } | ||
428 | + | ||
416 | private Map.Entry<K, Versioned<V>> mapRawEntry(Map.Entry<String, Versioned<byte[]>> e) { | 429 | private Map.Entry<K, Versioned<V>> mapRawEntry(Map.Entry<String, Versioned<byte[]>> e) { |
417 | return Maps.immutableEntry(dK(e.getKey()), e.getValue().<V>map(serializer::decode)); | 430 | return Maps.immutableEntry(dK(e.getKey()), e.getValue().<V>map(serializer::decode)); |
418 | } | 431 | } |
419 | 432 | ||
433 | + private Map.Entry<String, Versioned<byte[]>> reverseMapRawEntry(Map.Entry<K, Versioned<V>> e) { | ||
434 | + return Maps.immutableEntry(sK(e.getKey()), e.getValue().map(serializer::encode)); | ||
435 | + } | ||
436 | + | ||
420 | private CompletableFuture<UpdateResult<K, V>> updateAndGet(K key, | 437 | private CompletableFuture<UpdateResult<K, V>> updateAndGet(K key, |
421 | Match<V> oldValueMatch, | 438 | Match<V> oldValueMatch, |
422 | Match<Long> oldVersionMatch, | 439 | Match<Long> oldVersionMatch, |
423 | V value) { | 440 | V value) { |
424 | beforeUpdate(key); | 441 | beforeUpdate(key); |
425 | return database.mapUpdate(name, | 442 | return database.mapUpdate(name, |
426 | - keyCache.getUnchecked(key), | 443 | + sK(key), |
427 | oldValueMatch.map(serializer::encode), | 444 | oldValueMatch.map(serializer::encode), |
428 | oldVersionMatch, | 445 | oldVersionMatch, |
429 | value == null ? null : serializer.encode(value)) | 446 | value == null ? null : serializer.encode(value)) | ... | ... |
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.consistent.impl; | ||
17 | + | ||
18 | +import java.util.Arrays; | ||
19 | +import java.util.Collection; | ||
20 | +import java.util.Iterator; | ||
21 | +import java.util.Set; | ||
22 | +import java.util.function.Function; | ||
23 | +import java.util.stream.Collectors; | ||
24 | + | ||
25 | +import com.google.common.collect.Iterators; | ||
26 | + | ||
27 | +/** | ||
28 | + * Set view backed by Set with element type {@code <BACK>} but returns | ||
29 | + * element as {@code <OUT>} for convenience. | ||
30 | + * | ||
31 | + * @param <BACK> Backing {@link Set} element type. | ||
32 | + * MappingSet will follow this type's equality behavior. | ||
33 | + * @param <OUT> external facing element type. | ||
34 | + * MappingSet will ignores equality defined by this type. | ||
35 | + */ | ||
36 | +class MappingSet<BACK, OUT> implements Set<OUT> { | ||
37 | + | ||
38 | + private final Set<BACK> backedSet; | ||
39 | + private final Function<OUT, BACK> toBack; | ||
40 | + private final Function<BACK, OUT> toOut; | ||
41 | + | ||
42 | + public MappingSet(Set<BACK> backedSet, | ||
43 | + Function<Set<BACK>, Set<BACK>> supplier, | ||
44 | + Function<OUT, BACK> toBack, Function<BACK, OUT> toOut) { | ||
45 | + this.backedSet = supplier.apply(backedSet); | ||
46 | + this.toBack = toBack; | ||
47 | + this.toOut = toOut; | ||
48 | + } | ||
49 | + | ||
50 | + @Override | ||
51 | + public int size() { | ||
52 | + return backedSet.size(); | ||
53 | + } | ||
54 | + | ||
55 | + @Override | ||
56 | + public boolean isEmpty() { | ||
57 | + return backedSet.isEmpty(); | ||
58 | + } | ||
59 | + | ||
60 | + @Override | ||
61 | + public boolean contains(Object o) { | ||
62 | + return backedSet.contains(toBack.apply((OUT) o)); | ||
63 | + } | ||
64 | + | ||
65 | + @Override | ||
66 | + public Iterator<OUT> iterator() { | ||
67 | + return Iterators.transform(backedSet.iterator(), toOut::apply); | ||
68 | + } | ||
69 | + | ||
70 | + @Override | ||
71 | + public Object[] toArray() { | ||
72 | + return backedSet.stream() | ||
73 | + .map(toOut) | ||
74 | + .toArray(); | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public <T> T[] toArray(T[] a) { | ||
79 | + return backedSet.stream() | ||
80 | + .map(toOut) | ||
81 | + .toArray(size -> { | ||
82 | + if (size < a.length) { | ||
83 | + return (T[]) new Object[size]; | ||
84 | + } else { | ||
85 | + Arrays.fill(a, null); | ||
86 | + return a; | ||
87 | + } | ||
88 | + }); | ||
89 | + } | ||
90 | + | ||
91 | + @Override | ||
92 | + public boolean add(OUT e) { | ||
93 | + return backedSet.add(toBack.apply(e)); | ||
94 | + } | ||
95 | + | ||
96 | + @Override | ||
97 | + public boolean remove(Object o) { | ||
98 | + return backedSet.remove(toBack.apply((OUT) o)); | ||
99 | + } | ||
100 | + | ||
101 | + @Override | ||
102 | + public boolean containsAll(Collection<?> c) { | ||
103 | + return c.stream() | ||
104 | + .map(e -> toBack.apply((OUT) e)) | ||
105 | + .allMatch(backedSet::contains); | ||
106 | + } | ||
107 | + | ||
108 | + @Override | ||
109 | + public boolean addAll(Collection<? extends OUT> c) { | ||
110 | + return backedSet.addAll(c.stream().map(toBack).collect(Collectors.toList())); | ||
111 | + } | ||
112 | + | ||
113 | + @Override | ||
114 | + public boolean retainAll(Collection<?> c) { | ||
115 | + return backedSet.retainAll(c.stream() | ||
116 | + .map(x -> toBack.apply((OUT) x)) | ||
117 | + .collect(Collectors.toList())); | ||
118 | + } | ||
119 | + | ||
120 | + @Override | ||
121 | + public boolean removeAll(Collection<?> c) { | ||
122 | + return backedSet.removeAll(c.stream() | ||
123 | + .map(x -> toBack.apply((OUT) x)) | ||
124 | + .collect(Collectors.toList())); | ||
125 | + } | ||
126 | + | ||
127 | + @Override | ||
128 | + public void clear() { | ||
129 | + backedSet.clear(); | ||
130 | + } | ||
131 | +} |
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.consistent.impl; | ||
17 | + | ||
18 | +import static java.util.Collections.unmodifiableCollection; | ||
19 | +import static java.util.Collections.unmodifiableSet; | ||
20 | +import static org.hamcrest.Matchers.is; | ||
21 | +import static org.junit.Assert.*; | ||
22 | + | ||
23 | +import java.util.Collection; | ||
24 | +import java.util.Map; | ||
25 | +import java.util.Map.Entry; | ||
26 | +import java.util.Objects; | ||
27 | +import java.util.Set; | ||
28 | +import java.util.concurrent.CompletableFuture; | ||
29 | +import java.util.concurrent.ConcurrentHashMap; | ||
30 | +import java.util.function.Consumer; | ||
31 | + | ||
32 | +import org.junit.After; | ||
33 | +import org.junit.Before; | ||
34 | +import org.junit.Test; | ||
35 | +import org.onosproject.core.ApplicationId; | ||
36 | +import org.onosproject.core.DefaultApplicationId; | ||
37 | +import org.onosproject.store.service.Serializer; | ||
38 | +import org.onosproject.store.service.Transaction; | ||
39 | +import org.onosproject.store.service.Versioned; | ||
40 | + | ||
41 | +import com.google.common.base.MoreObjects; | ||
42 | +import com.google.common.collect.ImmutableMap; | ||
43 | +import com.google.common.collect.ImmutableSet; | ||
44 | +import net.kuujo.copycat.Task; | ||
45 | +import net.kuujo.copycat.cluster.Cluster; | ||
46 | +import net.kuujo.copycat.resource.ResourceState; | ||
47 | + | ||
48 | +/** | ||
49 | + * | ||
50 | + */ | ||
51 | +public class DefaultAsyncConsistentMapTest { | ||
52 | + | ||
53 | + private static final ApplicationId APP_ID = new DefaultApplicationId(42, "what"); | ||
54 | + | ||
55 | + private static final TestData KEY1A = new TestData("One", "a"); | ||
56 | + private static final TestData KEY1B = new TestData("One", "b"); | ||
57 | + | ||
58 | + private static final TestData VALUE2A = new TestData("Two", "a"); | ||
59 | + private static final TestData VALUE2B = new TestData("Two", "b"); | ||
60 | + | ||
61 | + @Before | ||
62 | + public void setUp() throws Exception { | ||
63 | + } | ||
64 | + | ||
65 | + @After | ||
66 | + public void tearDown() throws Exception { | ||
67 | + } | ||
68 | + | ||
69 | + @Test | ||
70 | + public void testKeySet() throws Exception { | ||
71 | + DefaultAsyncConsistentMap<TestData, TestData> map; | ||
72 | + String name = "map_name"; | ||
73 | + Database database = new TestDatabase(); | ||
74 | + Serializer serializer = Serializer.forTypes(TestData.class); | ||
75 | + | ||
76 | + map = new DefaultAsyncConsistentMap<>(name, APP_ID, database, serializer, | ||
77 | + false, false, false); | ||
78 | + map.put(KEY1A, VALUE2A); | ||
79 | + map.put(KEY1B, VALUE2A); | ||
80 | + | ||
81 | + Set<TestData> set = map.keySet().get(); | ||
82 | + assertEquals("Should contain 2 keys", | ||
83 | + 2, set.size()); | ||
84 | + assertThat(set.contains(KEY1A), is(true)); | ||
85 | + assertThat(set.contains(KEY1B), is(true)); | ||
86 | + assertThat(set.contains(new TestData("One", "a")), is(true)); | ||
87 | + } | ||
88 | + | ||
89 | + @Test | ||
90 | + public void testEntrySet() throws Exception { | ||
91 | + DefaultAsyncConsistentMap<TestData, TestData> map; | ||
92 | + String name = "map_name"; | ||
93 | + Database database = new TestDatabase(); | ||
94 | + Serializer serializer = Serializer.forTypes(TestData.class); | ||
95 | + | ||
96 | + map = new DefaultAsyncConsistentMap<>(name, APP_ID, database, serializer, | ||
97 | + false, false, false); | ||
98 | + map.put(KEY1A, VALUE2A); | ||
99 | + map.put(KEY1B, VALUE2A); | ||
100 | + | ||
101 | + assertEquals("Should contain 2 entry", | ||
102 | + 2, | ||
103 | + map.entrySet().get().size()); | ||
104 | + } | ||
105 | + | ||
106 | + /** | ||
107 | + * Object to be used as a test data. | ||
108 | + * | ||
109 | + * {@link Object#equals(Object)} use only part of it's fields. | ||
110 | + * | ||
111 | + * As a result there can be 2 instances which the | ||
112 | + * serialized bytes are not-equal but | ||
113 | + * {@link Object#equals(Object)}-wise they are equal. | ||
114 | + */ | ||
115 | + public static class TestData { | ||
116 | + | ||
117 | + private final String theKey; | ||
118 | + | ||
119 | + @SuppressWarnings("unused") | ||
120 | + private final String notUsedForEquals; | ||
121 | + | ||
122 | + public TestData(String theKey, String notUsedForEquals) { | ||
123 | + this.theKey = theKey; | ||
124 | + this.notUsedForEquals = notUsedForEquals; | ||
125 | + } | ||
126 | + | ||
127 | + @Override | ||
128 | + public int hashCode() { | ||
129 | + return Objects.hashCode(theKey); | ||
130 | + } | ||
131 | + | ||
132 | + @Override | ||
133 | + public boolean equals(Object obj) { | ||
134 | + if (obj instanceof TestData) { | ||
135 | + TestData that = (TestData) obj; | ||
136 | + return Objects.equals(this.theKey, that.theKey); | ||
137 | + } | ||
138 | + return false; | ||
139 | + } | ||
140 | + | ||
141 | + @Override | ||
142 | + public String toString() { | ||
143 | + return MoreObjects.toStringHelper(this) | ||
144 | + .add("theKey", theKey) | ||
145 | + .add("notUsedForEquals", notUsedForEquals) | ||
146 | + .toString(); | ||
147 | + } | ||
148 | + } | ||
149 | + | ||
150 | + /** | ||
151 | + * {@link Database} implementation for testing. | ||
152 | + * | ||
153 | + * There is only 1 backing Map, {@code mapName} will be ignored. | ||
154 | + */ | ||
155 | + public class TestDatabase implements Database { | ||
156 | + | ||
157 | + Map<String, Versioned<byte[]>> map = new ConcurrentHashMap<>(); | ||
158 | + | ||
159 | + @Override | ||
160 | + public CompletableFuture<Set<String>> maps() { | ||
161 | + return CompletableFuture.completedFuture(ImmutableSet.of()); | ||
162 | + } | ||
163 | + | ||
164 | + @Override | ||
165 | + public CompletableFuture<Map<String, Long>> counters() { | ||
166 | + return CompletableFuture.completedFuture(ImmutableMap.of()); | ||
167 | + } | ||
168 | + | ||
169 | + @Override | ||
170 | + public CompletableFuture<Integer> mapSize(String mapName) { | ||
171 | + return CompletableFuture.completedFuture(map.size()); | ||
172 | + } | ||
173 | + | ||
174 | + @Override | ||
175 | + public CompletableFuture<Boolean> mapIsEmpty(String mapName) { | ||
176 | + return CompletableFuture.completedFuture(map.isEmpty()); | ||
177 | + } | ||
178 | + | ||
179 | + @Override | ||
180 | + public CompletableFuture<Boolean> mapContainsKey(String mapName, | ||
181 | + String key) { | ||
182 | + return CompletableFuture.completedFuture(map.containsKey(key)); | ||
183 | + } | ||
184 | + | ||
185 | + @Override | ||
186 | + public CompletableFuture<Boolean> mapContainsValue(String mapName, | ||
187 | + byte[] value) { | ||
188 | + return CompletableFuture.completedFuture(map.containsValue(value)); | ||
189 | + } | ||
190 | + | ||
191 | + @Override | ||
192 | + public CompletableFuture<Versioned<byte[]>> mapGet(String mapName, | ||
193 | + String key) { | ||
194 | + return CompletableFuture.completedFuture(map.get(key)); | ||
195 | + } | ||
196 | + | ||
197 | + @Override | ||
198 | + public synchronized CompletableFuture<Result<UpdateResult<String, byte[]>>> mapUpdate(String mapName, | ||
199 | + String key, | ||
200 | + Match<byte[]> valueMatch, | ||
201 | + Match<Long> versionMatch, | ||
202 | + byte[] value) { | ||
203 | + | ||
204 | + boolean updated = false; | ||
205 | + final Versioned<byte[]> oldValue; | ||
206 | + final Versioned<byte[]> newValue; | ||
207 | + | ||
208 | + Versioned<byte[]> old = map.getOrDefault(key, new Versioned<byte[]>(null, 0)); | ||
209 | + if (valueMatch.matches(old.value()) && versionMatch.matches(old.version())) { | ||
210 | + updated = true; | ||
211 | + oldValue = old; | ||
212 | + newValue = new Versioned<>(value, old.version() + 1); | ||
213 | + map.put(key, newValue); | ||
214 | + } else { | ||
215 | + updated = false; | ||
216 | + oldValue = old; | ||
217 | + newValue = old; | ||
218 | + } | ||
219 | + return CompletableFuture.completedFuture( | ||
220 | + Result.ok(new UpdateResult<String, byte[]>(updated, | ||
221 | + mapName, key, oldValue, newValue))); | ||
222 | + } | ||
223 | + | ||
224 | + @Override | ||
225 | + public CompletableFuture<Result<Void>> mapClear(String mapName) { | ||
226 | + throw new UnsupportedOperationException(); | ||
227 | + } | ||
228 | + | ||
229 | + @Override | ||
230 | + public CompletableFuture<Set<String>> mapKeySet(String mapName) { | ||
231 | + return CompletableFuture.completedFuture(unmodifiableSet(map.keySet())); | ||
232 | + } | ||
233 | + | ||
234 | + @Override | ||
235 | + public CompletableFuture<Collection<Versioned<byte[]>>> mapValues(String mapName) { | ||
236 | + return CompletableFuture.completedFuture(unmodifiableCollection(map.values())); | ||
237 | + } | ||
238 | + | ||
239 | + @Override | ||
240 | + public CompletableFuture<Set<Entry<String, Versioned<byte[]>>>> mapEntrySet(String mapName) { | ||
241 | + return CompletableFuture.completedFuture(unmodifiableSet(map.entrySet())); | ||
242 | + } | ||
243 | + | ||
244 | + @Override | ||
245 | + public CompletableFuture<Long> counterAddAndGet(String counterName, | ||
246 | + long delta) { | ||
247 | + throw new UnsupportedOperationException(); | ||
248 | + } | ||
249 | + | ||
250 | + @Override | ||
251 | + public CompletableFuture<Long> counterGetAndAdd(String counterName, | ||
252 | + long delta) { | ||
253 | + throw new UnsupportedOperationException(); | ||
254 | + } | ||
255 | + | ||
256 | + @Override | ||
257 | + public CompletableFuture<Void> counterSet(String counterName, | ||
258 | + long value) { | ||
259 | + throw new UnsupportedOperationException(); | ||
260 | + } | ||
261 | + | ||
262 | + @Override | ||
263 | + public CompletableFuture<Boolean> counterCompareAndSet(String counterName, | ||
264 | + long expectedValue, | ||
265 | + long update) { | ||
266 | + throw new UnsupportedOperationException(); | ||
267 | + } | ||
268 | + | ||
269 | + @Override | ||
270 | + public CompletableFuture<Long> counterGet(String counterName) { | ||
271 | + throw new UnsupportedOperationException(); | ||
272 | + } | ||
273 | + | ||
274 | + @Override | ||
275 | + public CompletableFuture<Long> queueSize(String queueName) { | ||
276 | + throw new UnsupportedOperationException(); | ||
277 | + } | ||
278 | + | ||
279 | + @Override | ||
280 | + public CompletableFuture<Void> queuePush(String queueName, | ||
281 | + byte[] entry) { | ||
282 | + throw new UnsupportedOperationException(); | ||
283 | + } | ||
284 | + | ||
285 | + @Override | ||
286 | + public CompletableFuture<byte[]> queuePop(String queueName) { | ||
287 | + throw new UnsupportedOperationException(); | ||
288 | + } | ||
289 | + | ||
290 | + @Override | ||
291 | + public CompletableFuture<byte[]> queuePeek(String queueName) { | ||
292 | + throw new UnsupportedOperationException(); | ||
293 | + } | ||
294 | + | ||
295 | + @Override | ||
296 | + public CompletableFuture<CommitResponse> prepareAndCommit(Transaction transaction) { | ||
297 | + throw new UnsupportedOperationException(); | ||
298 | + } | ||
299 | + | ||
300 | + @Override | ||
301 | + public CompletableFuture<Boolean> prepare(Transaction transaction) { | ||
302 | + throw new UnsupportedOperationException(); | ||
303 | + } | ||
304 | + | ||
305 | + @Override | ||
306 | + public CompletableFuture<CommitResponse> commit(Transaction transaction) { | ||
307 | + throw new UnsupportedOperationException(); | ||
308 | + } | ||
309 | + | ||
310 | + @Override | ||
311 | + public CompletableFuture<Boolean> rollback(Transaction transaction) { | ||
312 | + throw new UnsupportedOperationException(); | ||
313 | + } | ||
314 | + | ||
315 | + @Override | ||
316 | + public String name() { | ||
317 | + return "name"; | ||
318 | + } | ||
319 | + | ||
320 | + @Override | ||
321 | + public ResourceState state() { | ||
322 | + return ResourceState.HEALTHY; | ||
323 | + } | ||
324 | + | ||
325 | + @Override | ||
326 | + public Cluster cluster() { | ||
327 | + throw new UnsupportedOperationException(); | ||
328 | + } | ||
329 | + | ||
330 | + @Override | ||
331 | + public Database addStartupTask(Task<CompletableFuture<Void>> task) { | ||
332 | + throw new UnsupportedOperationException(); | ||
333 | + } | ||
334 | + | ||
335 | + @Override | ||
336 | + public Database addShutdownTask(Task<CompletableFuture<Void>> task) { | ||
337 | + throw new UnsupportedOperationException(); | ||
338 | + } | ||
339 | + | ||
340 | + @Override | ||
341 | + public CompletableFuture<Database> open() { | ||
342 | + return CompletableFuture.completedFuture(this); | ||
343 | + } | ||
344 | + | ||
345 | + @Override | ||
346 | + public boolean isOpen() { | ||
347 | + return true; | ||
348 | + } | ||
349 | + | ||
350 | + @Override | ||
351 | + public CompletableFuture<Void> close() { | ||
352 | + return CompletableFuture.completedFuture(null); | ||
353 | + } | ||
354 | + | ||
355 | + @Override | ||
356 | + public boolean isClosed() { | ||
357 | + return false; | ||
358 | + } | ||
359 | + | ||
360 | + @Override | ||
361 | + public void registerConsumer(Consumer<StateMachineUpdate> consumer) { | ||
362 | + } | ||
363 | + | ||
364 | + @Override | ||
365 | + public void unregisterConsumer(Consumer<StateMachineUpdate> consumer) { | ||
366 | + } | ||
367 | + } | ||
368 | + | ||
369 | +} |
-
Please register or login to post a comment