/** * Copyright (C) 2013-2016 The Rythm Engine project * for LICENSE and other details see: * https://github.com/rythmengine/rythmengine */ package org.rythmengine.cache; import org.rythmengine.extension.ICacheService; import org.rythmengine.internal.RythmThreadFactory; import org.rythmengine.logger.ILogger; import org.rythmengine.logger.Logger; import org.rythmengine.utils.HashCode; import org.rythmengine.utils.S; import java.io.Serializable; import java.util.*; import java.util.concurrent.*; /** * A simple cache service implementation */ public class SimpleCacheService implements ICacheService { private static final ILogger logger = Logger.get(SimpleCacheService.class); public static final SimpleCacheService INSTANCE = new SimpleCacheService(); private static class TimerThreadFactory extends RythmThreadFactory { private TimerThreadFactory() { super("rythm-timer"); } } private ScheduledExecutorService scheduler = null; private SimpleCacheService() { startup(); } private static class Item implements Comparable<Item> { String key; Serializable value; long ts; int ttl; Item(String key, Serializable value, int ttl) { this.key = key; this.value = value; this.ttl = ttl; this.ts = System.currentTimeMillis(); } @Override public int hashCode() { return HashCode.hc(ttl, key, value); } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof Item) { Item that = (Item) obj; if (that.ttl != this.ttl) return false; String thisKey = this.key, thatKey = that.key; if (!S.isEqual(thisKey, thatKey)) return false; Object thatVal = that.value, thisVal = this.value; if (null == thatVal && null == thisVal) return true; return (null != thisVal) ? thisVal.equals(thatVal) : thatVal.equals(thisVal); } return false; } @Override public int compareTo(Item that) { return ttl - that.ttl; } } private ConcurrentHashMap<String, Item> cache_ = new ConcurrentHashMap<String, Item>(); private Queue<Item> items_ = new PriorityQueue<Item>(); @Override public void put(String key, Serializable value, int ttl) { if (null == key) throw new NullPointerException(); if (0 >= ttl) { ttl = defaultTTL; } Item item = cache_.get(key); if (null == item) { Item newItem = new Item(key, value, ttl); item = cache_.putIfAbsent(key, newItem); if (null != item) { item.value = value; item.ttl = ttl; } else { if (!items_.offer(newItem)) { throw new RuntimeException("oops, something is wrong"); }; } } else { item.value = value; item.ttl = ttl; } } @Override public void put(String key, Serializable value) { put(key, value, defaultTTL); } @Override public Serializable remove(String key) { Item item = cache_.remove(key); return null == item ? null : item.value; } @Override public void evict(String key) { cache_.remove(key); } @Override public void clear() { cache_.clear(); items_.clear(); } @Override public Serializable get(String key) { Item item = cache_.get(key); return null == item ? null : item.value; } @Override public boolean contains(String key) { return cache_.containsKey(key); } private int defaultTTL = 60; @Override public void setDefaultTTL(int ttl) { if (ttl == 0) throw new IllegalArgumentException("time to live value couldn't be zero"); this.defaultTTL = ttl; } @Override public void shutdown() { clear(); if (null != scheduler) { scheduler.shutdown(); scheduler = null; } } @Override public void startup() { if (null == scheduler) { scheduler = new ScheduledThreadPoolExecutor(1, new TimerThreadFactory()); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (items_.isEmpty()) { return; } long now = System.currentTimeMillis(); if (logger.isTraceEnabled()) { logger.trace(">>>>now:%s", now); } while(true) { Item item = items_.peek(); if (null == item) { break; } long ts = item.ts + ((long)item.ttl) * 1000; if ((ts) < now + 50) { items_.poll(); cache_.remove(item.key); if (Logger.isTraceEnabled()) { logger.trace("- %s at %s", item.key, ts); } continue; } else { if (Logger.isTraceEnabled()) { logger.trace(">>>>ts: %s", ts); } } break; } } }, 0, 100, TimeUnit.MILLISECONDS); } } @Override protected void finalize() throws Throwable { shutdown(); } }