1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.storage.repository;
18
19 import static java.util.Objects.requireNonNull;
20
21 import java.util.concurrent.CompletableFuture;
22
23 import javax.annotation.Nullable;
24
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
29 import com.github.benmanes.caffeine.cache.Caffeine;
30 import com.github.benmanes.caffeine.cache.CaffeineSpec;
31 import com.github.benmanes.caffeine.cache.Weigher;
32 import com.github.benmanes.caffeine.cache.stats.CacheStats;
33 import com.google.common.base.MoreObjects;
34
35 import io.micrometer.core.instrument.MeterRegistry;
36 import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
37
38 public class RepositoryCache {
39
40 private static final Logger logger = LoggerFactory.getLogger(RepositoryCache.class);
41
42 @Nullable
43 public static String validateCacheSpec(@Nullable String cacheSpec) {
44 if (cacheSpec == null) {
45 return null;
46 }
47
48 try {
49 CaffeineSpec.parse(cacheSpec);
50 return cacheSpec;
51 } catch (IllegalArgumentException e) {
52 throw new IllegalArgumentException("cacheSpec: " + cacheSpec + " (" + e.getMessage() + ')');
53 }
54 }
55
56 @SuppressWarnings("rawtypes")
57 private final AsyncLoadingCache<CacheableCall, Object> cache;
58 private final String cacheSpec;
59
60 @SuppressWarnings({ "rawtypes", "unchecked" })
61 public RepositoryCache(String cacheSpec, MeterRegistry meterRegistry) {
62 this.cacheSpec = requireNonNull(validateCacheSpec(cacheSpec), "cacheSpec");
63 requireNonNull(meterRegistry, "meterRegistry");
64
65 final Caffeine<Object, Object> builder = Caffeine.from(cacheSpec);
66 if (cacheSpec.contains("maximumWeight=")) {
67 builder.weigher((Weigher<CacheableCall, Object>) CacheableCall::weigh);
68 }
69 cache = builder.recordStats()
70 .buildAsync((key, executor) -> {
71 logger.debug("Cache miss: {}", key);
72 return key.execute();
73 });
74
75 CaffeineCacheMetrics.monitor(meterRegistry, cache, "repository");
76 }
77
78 public <T> CompletableFuture<T> get(CacheableCall<T> call) {
79 requireNonNull(call, "call");
80 @SuppressWarnings("unchecked")
81 final CompletableFuture<T> f = (CompletableFuture<T>) cache.get(call);
82 return f;
83 }
84
85 @Nullable
86 public <T> CompletableFuture<T> getIfPresent(CacheableCall<T> call) {
87 requireNonNull(call, "call");
88 @SuppressWarnings("unchecked")
89 final CompletableFuture<T> f = (CompletableFuture<T>) cache.getIfPresent(call);
90 return f;
91 }
92
93 public <T> void put(CacheableCall<T> call, T value) {
94 requireNonNull(call, "call");
95 requireNonNull(value, "value");
96 cache.put(call, CompletableFuture.completedFuture(value));
97 }
98
99 public CacheStats stats() {
100 return cache.synchronous().stats();
101 }
102
103 public void clear() {
104 cache.synchronous().invalidateAll();
105 }
106
107 @Override
108 public String toString() {
109 return MoreObjects.toStringHelper(this)
110 .add("spec", cacheSpec)
111 .add("stats", stats())
112 .toString();
113 }
114 }