package org.crosswire.webtools; import org.crosswire.utils.Utils; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * Generic, multipurpose cache utility * Created by scribe on 6/12/17. */ public class ObjectCache { // we're defaulting caching to off for now until we determine it should be used private static final long DEFAULT_TTL = 0; // off; future might be: 60 * 60 * 1000; // 1 hour // OK, we we're defaulting our cache type to REQUEST_CACHE because we initially made this mechanism to cache certain request. // This is merely the default, so any cache not specify a cacheName or explicit TTL will get a TTL = the system paramer: REQUEST_CACHE_MILLISECONDS_TO_LIVE // Maybe we should take this out one day, but as a default in what is primarily a web service, it's not a bad default. private static final long REQUEST_CACHE_MILLISECONDS_TO_LIVE = Long.parseLong(Optional.ofNullable(Utils.getSysConfig().getProperty("REQUEST_CACHE_MILLISECONDS_TO_LIVE")).orElse(Long.toString(DEFAULT_TTL))); // milliseconds private long ttl = REQUEST_CACHE_MILLISECONDS_TO_LIVE; class CachedObject { long lastHit = 0; E data = null; CachedObject(E data) { this(System.currentTimeMillis(), data); } CachedObject(long lastHit, E data) { this.lastHit = lastHit; this.data = data; } } public ObjectCache() { this(null); } /** * Allow cache naming, which will retrieve TTL from system parameter: CACHE_ + cacheName + _MILLISECONDS_TO_LIVE * @param cacheName name of cache */ public ObjectCache(String cacheName) { if (cacheName != null) { // if we name our cache, then lookup TTL by cache name. If not supplied, then use DEFAULT_TTL. ttl = Long.parseLong(Optional.ofNullable(Utils.getSysConfig().getProperty("CACHE_" + cacheName + "_MILLISECONDS_TO_LIVE")).orElse(Long.toString(DEFAULT_TTL))); // milliseconds } } /** * allow explicit ttl setting * @param ttl cache objects time to live in milliseconds */ public ObjectCache(long ttl) { this.ttl = ttl; } private Map cachedObjects = new ConcurrentHashMap<>(); /** * get an item from our cache, if it exists, otherwise null * @param key - cache key * @return item associated with key, or else null if none exists */ public E get(String key) { // assert cache is on if (ttl == 0) return null; // remove stale items long flushTime = System.currentTimeMillis() - ttl; cachedObjects = cachedObjects.entrySet().stream().filter(r -> r.getValue().lastHit > flushTime).collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); // see if we have a cache hit and return it // We are not refreshing item stale time on read access so we don't update lastHit here. // but in the future, we might make this configurable for other uses. return Optional.ofNullable(cachedObjects.get(key)).orElse(new CachedObject(0, null)).data; } /** * add an item to our cache * @param key key with which to associate data * @param data data to associate to a key */ public void put(String key, E data) { // assert cache is on if (ttl == 0) return; cachedObjects.put(key, new CachedObject(data)); } }