|
1 | 1 | package redis.clients.jedis;
|
2 | 2 |
|
3 | 3 | import java.nio.ByteBuffer;
|
4 |
| -import java.util.HashMap; |
| 4 | +import java.util.HashSet; |
5 | 5 | import java.util.List;
|
6 | 6 | import java.util.Map;
|
7 |
| - |
8 |
| -import redis.clients.jedis.exceptions.JedisException; |
| 7 | +import java.util.Set; |
| 8 | +import java.util.concurrent.ConcurrentHashMap; |
| 9 | +import java.util.function.Function; |
9 | 10 | import redis.clients.jedis.util.SafeEncoder;
|
10 | 11 |
|
11 |
| -public class ClientSideCache { |
| 12 | +/** |
| 13 | + * The class to manage the client-side caching. User can provide any of implementation of this class to the client |
| 14 | + * object; e.g. {@link redis.clients.jedis.util.CaffeineCSC CaffeineCSC} or |
| 15 | + * {@link redis.clients.jedis.util.GuavaCSC GuavaCSC} or a custom implementation of their own. |
| 16 | + */ |
| 17 | +public abstract class ClientSideCache { |
12 | 18 |
|
13 |
| - private final Map<ByteBuffer, Object> cache; |
| 19 | + protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; |
| 20 | + protected static final int DEFAULT_EXPIRE_SECONDS = 100; |
14 | 21 |
|
15 |
| - public ClientSideCache() { |
16 |
| - this.cache = new HashMap<>(); |
17 |
| - } |
| 22 | + private final Map<ByteBuffer, Set<Long>> keyToCommandHashes; |
18 | 23 |
|
19 |
| - /** |
20 |
| - * For testing purpose only. |
21 |
| - * @param map |
22 |
| - */ |
23 |
| - ClientSideCache(Map<ByteBuffer, Object> map) { |
24 |
| - this.cache = map; |
| 24 | + protected ClientSideCache() { |
| 25 | + this.keyToCommandHashes = new ConcurrentHashMap<>(); |
25 | 26 | }
|
26 | 27 |
|
| 28 | + protected abstract void invalidateAllCommandHashes(); |
| 29 | + |
| 30 | + protected abstract void invalidateCommandHashes(Iterable<Long> hashes); |
| 31 | + |
| 32 | + protected abstract void put(long hash, Object value); |
| 33 | + |
| 34 | + protected abstract Object get(long hash); |
| 35 | + |
| 36 | + protected abstract long getCommandHash(CommandObject command); |
| 37 | + |
27 | 38 | public final void clear() {
|
28 |
| - cache.clear(); |
| 39 | + invalidateAllKeysAndCommandHashes(); |
29 | 40 | }
|
30 | 41 |
|
31 |
| - public final void invalidateKeys(List list) { |
| 42 | + final void invalidate(List list) { |
32 | 43 | if (list == null) {
|
33 |
| - clear(); |
| 44 | + invalidateAllKeysAndCommandHashes(); |
34 | 45 | return;
|
35 | 46 | }
|
36 | 47 |
|
37 |
| - list.forEach(this::invalidateKey); |
| 48 | + list.forEach(this::invalidateKeyAndRespectiveCommandHashes); |
38 | 49 | }
|
39 | 50 |
|
40 |
| - private void invalidateKey(Object key) { |
41 |
| - if (key instanceof byte[]) { |
42 |
| - cache.remove(convertKey((byte[]) key)); |
43 |
| - } else { |
44 |
| - throw new JedisException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); |
45 |
| - } |
| 51 | + private void invalidateAllKeysAndCommandHashes() { |
| 52 | + invalidateAllCommandHashes(); |
| 53 | + keyToCommandHashes.clear(); |
46 | 54 | }
|
47 | 55 |
|
48 |
| - protected void setKey(Object key, Object value) { |
49 |
| - cache.put(getMapKey(key), value); |
50 |
| - } |
| 56 | + private void invalidateKeyAndRespectiveCommandHashes(Object key) { |
| 57 | + if (!(key instanceof byte[])) { |
| 58 | + throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); |
| 59 | + } |
51 | 60 |
|
52 |
| - protected <T> T getValue(Object key) { |
53 |
| - return (T) getMapValue(key); |
54 |
| - } |
| 61 | + final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); |
55 | 62 |
|
56 |
| - private Object getMapValue(Object key) { |
57 |
| - return cache.get(getMapKey(key)); |
| 63 | + Set<Long> hashes = keyToCommandHashes.get(mapKey); |
| 64 | + if (hashes != null) { |
| 65 | + invalidateCommandHashes(hashes); |
| 66 | + keyToCommandHashes.remove(mapKey); |
| 67 | + } |
58 | 68 | }
|
59 | 69 |
|
60 |
| - private ByteBuffer getMapKey(Object key) { |
61 |
| - if (key instanceof byte[]) { |
62 |
| - return convertKey((byte[]) key); |
63 |
| - } else { |
64 |
| - return convertKey(SafeEncoder.encode(String.valueOf(key))); |
| 70 | + final <T> T getValue(Function<CommandObject<T>, T> loader, CommandObject<T> command, String... keys) { |
| 71 | + |
| 72 | + final long hash = getCommandHash(command); |
| 73 | + |
| 74 | + T value = (T) get(hash); |
| 75 | + if (value != null) { |
| 76 | + return value; |
65 | 77 | }
|
| 78 | + |
| 79 | + value = loader.apply(command); |
| 80 | + if (value != null) { |
| 81 | + put(hash, value); |
| 82 | + for (String key : keys) { |
| 83 | + ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key); |
| 84 | + if (keyToCommandHashes.containsKey(mapKey)) { |
| 85 | + keyToCommandHashes.get(mapKey).add(hash); |
| 86 | + } else { |
| 87 | + Set<Long> set = new HashSet<>(); |
| 88 | + set.add(hash); |
| 89 | + keyToCommandHashes.put(mapKey, set); |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + return value; |
| 95 | + } |
| 96 | + |
| 97 | + private ByteBuffer makeKeyForKeyToCommandHashes(String key) { |
| 98 | + return makeKeyForKeyToCommandHashes(SafeEncoder.encode(key)); |
66 | 99 | }
|
67 | 100 |
|
68 |
| - private static ByteBuffer convertKey(byte[] b) { |
| 101 | + private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) { |
69 | 102 | return ByteBuffer.wrap(b);
|
70 | 103 | }
|
71 | 104 | }
|
0 commit comments