GuavaCacheAdapter.java

  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. import io.wcm.caravan.pipeline.cache.CachePersistencyOptions;
  22. import io.wcm.caravan.pipeline.cache.spi.CacheAdapter;

  23. import java.math.BigDecimal;
  24. import java.util.Map;

  25. import org.apache.felix.scr.annotations.Activate;
  26. import org.apache.felix.scr.annotations.Component;
  27. import org.apache.felix.scr.annotations.Deactivate;
  28. import org.apache.felix.scr.annotations.Property;
  29. import org.apache.felix.scr.annotations.Reference;
  30. import org.apache.felix.scr.annotations.Service;
  31. import org.apache.sling.commons.osgi.PropertiesUtil;
  32. import org.osgi.framework.Constants;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;

  35. import rx.Observable;

  36. import com.codahale.metrics.Counter;
  37. import com.codahale.metrics.MetricRegistry;
  38. import com.codahale.metrics.Timer;
  39. import com.google.common.cache.Cache;
  40. import com.google.common.cache.CacheBuilder;
  41. import com.google.common.cache.Weigher;

  42. /**
  43.  * {@link CacheAdapter} implementation for Guava.
  44.  * Provides guava {@link Cache}, which size is specified in bytes. Default cache size is 10 MB. Provide higher property
  45.  * value {@value #MAX_CACHE_SIZE_MB_PROPERTY} to set up higher cache capacity.
  46.  * items life time depends on the amount and size of stored cache items. Items, which capacity is higher than 1/4 of the
  47.  * declared cache size will not be stored.
  48.  */
  49. @Component(immediate = true, metatype = true,
  50. label = "wcm.io Caravan Pipeline Cache Adapter for Guava",
  51. description = "Configure pipeline caching in guava.")
  52. @Service(CacheAdapter.class)
  53. public class GuavaCacheAdapter implements CacheAdapter {

  54.   private static final Logger log = LoggerFactory.getLogger(GuavaCacheAdapter.class);

  55.   @Property(label = "Service Ranking", intValue = GuavaCacheAdapter.DEFAULT_RANKING,
  56.       description = "Used to determine the order of caching layers if you are using multiple Cache Adapters. "
  57.           + "Fast system-internal caches should have a lower service than slower network caches, so that they are queried first.",
  58.           propertyPrivate = false)
  59.   static final String PROPERTY_RANKING = Constants.SERVICE_RANKING;
  60.   static final int DEFAULT_RANKING = 1000;

  61.   @Property(label = "Max. size in MB",
  62.       description = "Declares the maximum total amount of VM memory in Megabyte that will be used by this cache adapter.")
  63.   static final String MAX_CACHE_SIZE_MB_PROPERTY = "maxCacheSizeMB";
  64.   private static final Integer MAX_CACHE_SIZE_MB_DEFAULT = 10;

  65.   @Property(label = "Enabled",
  66.       description = "Enables or disables the whole cache adapter and all operations.",
  67.       boolValue = GuavaCacheAdapter.CACHE_ENABLED_DEFAULT)
  68.   static final String CACHE_ENABLED_PROPERTY = "enabled";
  69.   private static final boolean CACHE_ENABLED_DEFAULT = true;

  70.   /**
  71.    * 1024*1024 multiplier used to provide bytes from megabyte values to create correct cache weight
  72.    */
  73.   private static final BigDecimal WEIGHT_MULTIPLIER = new BigDecimal(1048576);

  74.   private Cache<String, String> guavaCache;
  75.   private long cacheWeightInBytes;
  76.   private boolean enabled;

  77.   @Reference
  78.   private MetricRegistry metricRegistry;
  79.   private Timer getLatencyTimer;
  80.   private Timer putLatencyTimer;
  81.   private Counter hitsCounter;
  82.   private Counter missesCounter;

  83.   @Activate
  84.   void activate(Map<String, Object> config) {
  85.     cacheWeightInBytes = new BigDecimal(PropertiesUtil.toDouble(config.get(MAX_CACHE_SIZE_MB_PROPERTY), MAX_CACHE_SIZE_MB_DEFAULT))
  86.     .multiply(WEIGHT_MULTIPLIER).longValue();
  87.     this.guavaCache = CacheBuilder.newBuilder().weigher(new Weigher<String, String>() {
  88.       @Override
  89.       public int weigh(String key, String value) {
  90.         return getWeight(key) + getWeight(value);
  91.       }
  92.     }).maximumWeight(cacheWeightInBytes).build();
  93.     enabled = PropertiesUtil.toBoolean(config.get(CACHE_ENABLED_PROPERTY), CACHE_ENABLED_DEFAULT);

  94.     getLatencyTimer = metricRegistry.timer(MetricRegistry.name(getClass(), "latency", "get"));
  95.     putLatencyTimer = metricRegistry.timer(MetricRegistry.name(getClass(), "latency", "put"));
  96.     hitsCounter = metricRegistry.counter(MetricRegistry.name(getClass(), "hits"));
  97.     missesCounter = metricRegistry.counter(MetricRegistry.name(getClass(), "misses"));
  98.   }

  99.   private int getWeight(String toMeasure) {
  100.     return 8 * ((((toMeasure.length()) * 2) + 45) / 8);
  101.   }

  102.   @Deactivate
  103.   void deactivate() {
  104.     metricRegistry.remove(MetricRegistry.name(getClass(), "latency", "get"));
  105.     metricRegistry.remove(MetricRegistry.name(getClass(), "latency", "put"));
  106.     metricRegistry.remove(MetricRegistry.name(getClass(), "hits"));
  107.     metricRegistry.remove(MetricRegistry.name(getClass(), "misses"));
  108.   }

  109.   @Override
  110.   public Observable<String> get(String cacheKey, CachePersistencyOptions options) {
  111.     if (!enabled || !options.shouldUseTransientCaches()) {
  112.       return Observable.empty();
  113.     }

  114.     return Observable.create(subscriber -> {
  115.       Timer.Context context = getLatencyTimer.time();
  116.       String cacheEntry = guavaCache.getIfPresent(cacheKey);
  117.       if (cacheEntry != null) {
  118.         hitsCounter.inc();
  119.         subscriber.onNext(cacheEntry);
  120.       }
  121.       else {
  122.         missesCounter.inc();
  123.       }
  124.       context.stop();
  125.       log.trace("Succesfully retrieved document with id {}: {}", cacheKey, cacheEntry);
  126.       subscriber.onCompleted();
  127.     });

  128.   }

  129.   @Override
  130.   public void put(String cacheKey, String jsonString, CachePersistencyOptions options) {
  131.     if (!enabled || !options.shouldUseTransientCaches()) {
  132.       return;
  133.     }

  134.     Timer.Context context = putLatencyTimer.time();
  135.     guavaCache.put(cacheKey, jsonString);
  136.     context.stop();
  137.     log.trace("Succesfully put document into Guava cache with id {}:\n{}", cacheKey, jsonString);

  138.   }

  139. }