package com.rubiconproject.oss.kv.backends; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.rubiconproject.oss.kv.BaseManagedKeyValueStore; import com.rubiconproject.oss.kv.KeyValueStore; import com.rubiconproject.oss.kv.KeyValueStoreException; import com.rubiconproject.oss.kv.annotations.Configurable; import com.rubiconproject.oss.kv.annotations.Configurable.Type; import com.rubiconproject.oss.kv.transcoder.Transcoder; public class CachingKeyValueStore extends BaseManagedKeyValueStore { public static final String IDENTIFIER = "caching"; protected Log log = LogFactory.getLog(getClass()); protected KeyValueStore master; protected KeyValueStore cache; protected boolean cacheOnMiss = true; protected boolean cacheOnSet = true; public CachingKeyValueStore() { } public CachingKeyValueStore(KeyValueStore master, KeyValueStore cache) { this.master = master; this.cache = cache; } public void setMaster(KeyValueStore master) { this.master = master; } public void setCache(KeyValueStore cache) { this.cache = cache; } @Configurable(name = "cacheOnMiss", accepts = Type.BooleanType) public void setCacheOnMiss(boolean cacheOnMiss) { this.cacheOnMiss = cacheOnMiss; } @Configurable(name = "cacheOnSet", accepts = Type.BooleanType) public void setCacheOnSet(boolean cacheOnSet) { this.cacheOnSet = cacheOnSet; } public String getIdentifier() { return IDENTIFIER; } public void start() throws IOException { super.start(); } public boolean exists(String key) throws KeyValueStoreException, IOException { assertReadable(); boolean b = false; try { b = cache.exists(key); } catch (Exception e) { log.warn("Unable to call exists() on cache: " + e.getMessage()); } if (!b) b = master.exists(key); return b; } public Object get(String key) throws KeyValueStoreException, IOException { assertReadable(); Object obj = null; try { obj = cache.get(key); } catch (Exception e) { log.warn("Unable to call get() on cache: " + e.getMessage()); } if (obj == null) { obj = master.get(key); if ((obj != null) && (cacheOnMiss)) { try { cache.set(key, (Object) obj); } catch (Exception e) { log .warn("Unable to call set() on cache: " + e.getMessage()); } } } return obj; } public Object get(String key, Transcoder transcoder) throws KeyValueStoreException, IOException { assertReadable(); Object obj = null; try { obj = cache.get(key, transcoder); } catch (Exception e) { log.warn("Unable to call get() on cache: " + e.getMessage()); } if (obj == null) { obj = master.get(key, transcoder); if ((obj != null) && (cacheOnMiss)) { try { cache.set(key, (Object) obj, transcoder); } catch (KeyValueStoreException e) { if ((e.getCause() != null) && (e.getCause().getClass() .equals(TimeoutException.class))) { log .warn("Unable to call set() on cache due to TimeoutException: " + e.getMessage()); } else { log.warn("Unable to call set() on cache", e); } } catch (Exception e) { log.warn("Unable to call set() on cache", e); } } } return obj; } public Map<String, Object> getBulk(String... keys) throws KeyValueStoreException, IOException { List<String> coll = Arrays.asList(keys); return getBulk(coll); } public Map<String, Object> getBulk(List<String> keys) throws KeyValueStoreException, IOException { assertReadable(); Map<String, Object> results = new HashMap<String, Object>(); try { Map<String, Object> cached = cache.getBulk(keys); results.putAll(cached); } catch (Exception e) { log.warn("Unable to call getBulk() on cache: " + e.getMessage()); } if (results.size() < keys.size()) { // find all keys not in cache List<String> backendQueryKeys = new ArrayList<String>(keys.size() - results.size()); for (String key : keys) { if (!results.containsKey(key)) backendQueryKeys.add(key); } Map<String, Object> backendResults = master .getBulk(backendQueryKeys); results.putAll(backendResults); } return results; } public Map<String, Object> getBulk(List<String> keys, Transcoder transcoder) throws KeyValueStoreException, IOException { assertReadable(); Map<String, Object> results = new HashMap<String, Object>(); try { Map<String, Object> cached = cache.getBulk(keys, transcoder); results.putAll(cached); } catch (Exception e) { log.warn("Unable to call getBulk() on cache: " + e.getMessage()); } if (results.size() < keys.size()) { // find all keys not in cache List<String> backendQueryKeys = new ArrayList<String>(keys.size() - results.size()); for (String key : keys) { if (!results.containsKey(key)) backendQueryKeys.add(key); } Map<String, Object> backendResults = master.getBulk( backendQueryKeys, transcoder); results.putAll(backendResults); } return results; } public void set(String key, Object value) throws KeyValueStoreException, IOException { assertWriteable(); try { if (cacheOnSet) { cache.set(key, value); } else cache.delete(key); } catch (Exception e) { log.warn("Unable to call set() on cache: " + e.getMessage()); } master.set(key, value); } public void set(String key, Object value, Transcoder transcoder) throws KeyValueStoreException, IOException { assertWriteable(); try { if (cacheOnSet) cache.set(key, value, transcoder); else cache.delete(key); } catch (Exception e) { log.warn("Unable to call set() on cache: " + e.getMessage()); } master.set(key, value, transcoder); } public void delete(String key) throws KeyValueStoreException, IOException { assertWriteable(); try { cache.delete(key); } catch (Exception e) { log.warn("Unable to call delete() on cache: " + e.getMessage()); } master.delete(key); } }