View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
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   * {@link CacheAdapter} implementation for Guava.
50   * Provides guava {@link Cache}, which size is specified in bytes. Default cache size is 10 MB. Provide higher property
51   * value {@value #MAX_CACHE_SIZE_MB_PROPERTY} to set up higher cache capacity.
52   * items life time depends on the amount and size of stored cache items. Items, which capacity is higher than 1/4 of the
53   * declared cache size will not be stored.
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     * 1024*1024 multiplier used to provide bytes from megabyte values to create correct cache weight
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 }