1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.caravan.pipeline.cache.guava.impl;
21
22 import io.wcm.caravan.pipeline.cache.CachePersistencyOptions;
23 import io.wcm.caravan.pipeline.cache.spi.CacheAdapter;
24
25 import java.math.BigDecimal;
26 import java.util.Map;
27
28 import org.apache.felix.scr.annotations.Activate;
29 import org.apache.felix.scr.annotations.Component;
30 import org.apache.felix.scr.annotations.Deactivate;
31 import org.apache.felix.scr.annotations.Property;
32 import org.apache.felix.scr.annotations.Reference;
33 import org.apache.felix.scr.annotations.Service;
34 import org.apache.sling.commons.osgi.PropertiesUtil;
35 import org.osgi.framework.Constants;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import rx.Observable;
40
41 import com.codahale.metrics.Counter;
42 import com.codahale.metrics.MetricRegistry;
43 import com.codahale.metrics.Timer;
44 import com.google.common.cache.Cache;
45 import com.google.common.cache.CacheBuilder;
46 import com.google.common.cache.Weigher;
47
48
49
50
51
52
53
54
55 @Component(immediate = true, metatype = true,
56 label = "wcm.io Caravan Pipeline Cache Adapter for Guava",
57 description = "Configure pipeline caching in guava.")
58 @Service(CacheAdapter.class)
59 public class GuavaCacheAdapter implements CacheAdapter {
60
61 private static final Logger log = LoggerFactory.getLogger(GuavaCacheAdapter.class);
62
63 @Property(label = "Service Ranking", intValue = GuavaCacheAdapter.DEFAULT_RANKING,
64 description = "Used to determine the order of caching layers if you are using multiple Cache Adapters. "
65 + "Fast system-internal caches should have a lower service than slower network caches, so that they are queried first.",
66 propertyPrivate = false)
67 static final String PROPERTY_RANKING = Constants.SERVICE_RANKING;
68 static final int DEFAULT_RANKING = 1000;
69
70 @Property(label = "Max. size in MB",
71 description = "Declares the maximum total amount of VM memory in Megabyte that will be used by this cache adapter.")
72 static final String MAX_CACHE_SIZE_MB_PROPERTY = "maxCacheSizeMB";
73 private static final Integer MAX_CACHE_SIZE_MB_DEFAULT = 10;
74
75 @Property(label = "Enabled",
76 description = "Enables or disables the whole cache adapter and all operations.",
77 boolValue = GuavaCacheAdapter.CACHE_ENABLED_DEFAULT)
78 static final String CACHE_ENABLED_PROPERTY = "enabled";
79 private static final boolean CACHE_ENABLED_DEFAULT = true;
80
81
82
83
84 private static final BigDecimal WEIGHT_MULTIPLIER = new BigDecimal(1048576);
85
86 private Cache<String, String> guavaCache;
87 private long cacheWeightInBytes;
88 private boolean enabled;
89
90 @Reference
91 private MetricRegistry metricRegistry;
92 private Timer getLatencyTimer;
93 private Timer putLatencyTimer;
94 private Counter hitsCounter;
95 private Counter missesCounter;
96
97 @Activate
98 void activate(Map<String, Object> config) {
99 cacheWeightInBytes = new BigDecimal(PropertiesUtil.toDouble(config.get(MAX_CACHE_SIZE_MB_PROPERTY), MAX_CACHE_SIZE_MB_DEFAULT))
100 .multiply(WEIGHT_MULTIPLIER).longValue();
101 this.guavaCache = CacheBuilder.newBuilder().weigher(new Weigher<String, String>() {
102 @Override
103 public int weigh(String key, String value) {
104 return getWeight(key) + getWeight(value);
105 }
106 }).maximumWeight(cacheWeightInBytes).build();
107 enabled = PropertiesUtil.toBoolean(config.get(CACHE_ENABLED_PROPERTY), CACHE_ENABLED_DEFAULT);
108
109 getLatencyTimer = metricRegistry.timer(MetricRegistry.name(getClass(), "latency", "get"));
110 putLatencyTimer = metricRegistry.timer(MetricRegistry.name(getClass(), "latency", "put"));
111 hitsCounter = metricRegistry.counter(MetricRegistry.name(getClass(), "hits"));
112 missesCounter = metricRegistry.counter(MetricRegistry.name(getClass(), "misses"));
113 }
114
115 private int getWeight(String toMeasure) {
116 return 8 * ((((toMeasure.length()) * 2) + 45) / 8);
117 }
118
119 @Deactivate
120 void deactivate() {
121 metricRegistry.remove(MetricRegistry.name(getClass(), "latency", "get"));
122 metricRegistry.remove(MetricRegistry.name(getClass(), "latency", "put"));
123 metricRegistry.remove(MetricRegistry.name(getClass(), "hits"));
124 metricRegistry.remove(MetricRegistry.name(getClass(), "misses"));
125 }
126
127 @Override
128 public Observable<String> get(String cacheKey, CachePersistencyOptions options) {
129 if (!enabled || !options.shouldUseTransientCaches()) {
130 return Observable.empty();
131 }
132
133 return Observable.create(subscriber -> {
134 Timer.Context context = getLatencyTimer.time();
135 String cacheEntry = guavaCache.getIfPresent(cacheKey);
136 if (cacheEntry != null) {
137 hitsCounter.inc();
138 subscriber.onNext(cacheEntry);
139 }
140 else {
141 missesCounter.inc();
142 }
143 context.stop();
144 log.trace("Succesfully retrieved document with id {}: {}", cacheKey, cacheEntry);
145 subscriber.onCompleted();
146 });
147
148 }
149
150 @Override
151 public void put(String cacheKey, String jsonString, CachePersistencyOptions options) {
152 if (!enabled || !options.shouldUseTransientCaches()) {
153 return;
154 }
155
156 Timer.Context context = putLatencyTimer.time();
157 guavaCache.put(cacheKey, jsonString);
158 context.stop();
159 log.trace("Succesfully put document into Guava cache with id {}:\n{}", cacheKey, jsonString);
160
161 }
162
163 }