|
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