All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.cache.DefaultServerCache Maven / Gradle / Ivy

There is a newer version: 2.8.1
Show newest version
package com.avaje.ebeaninternal.server.cache;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheStatistics;


/**
 * The default cache implementation.
 * 

* It is base on ConcurrentHashMap with periodic trimming using a TimerTask. * The periodic trimming means that an LRU list does not have to be maintained. *

*/ public class DefaultServerCache implements ServerCache { private static final Logger logger = Logger.getLogger(DefaultServerCache.class.getName()); private static final CacheEntryComparator comparator = new CacheEntryComparator(); private final ConcurrentHashMap map = new ConcurrentHashMap(); private final AtomicInteger missCount = new AtomicInteger(); private final AtomicInteger removedHitCount = new AtomicInteger(); private final Object monitor = new Object(); private final String name; private int maxSize; private long trimFrequency; private int maxIdleSecs; private int maxSecsToLive; public DefaultServerCache(String name, ServerCacheOptions options) { this(name, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive()); } public DefaultServerCache(String name, int maxSize, int maxIdleSecs, int maxSecsToLive) { this.name = name; this.maxSize = maxSize; this.maxIdleSecs = maxIdleSecs; this.maxSecsToLive = maxSecsToLive; this.trimFrequency = 60; } public void init(EbeanServer server) { TrimTask trim = new TrimTask(); BackgroundExecutor executor = server.getBackgroundExecutor(); executor.executePeriodically(trim, trimFrequency, TimeUnit.SECONDS); } public ServerCacheStatistics getStatistics(boolean reset) { ServerCacheStatistics s = new ServerCacheStatistics(); s.setCacheName(name); s.setMaxSize(maxSize); // these counters won't necessarily be consistent with // respect to each other as activity can occur while // they are being calculated int mc = reset ? missCount.getAndSet(0) : missCount.get(); int hc = getHitCount(reset); int size = size(); s.setSize(size); s.setHitCount(hc); s.setMissCount(mc); return s; } public int getHitRatio() { int mc = missCount.get(); int hc = getHitCount(false); int totalCount = hc + mc; if (totalCount == 0){ return 0; } else { return hc * 100 / totalCount; } } private int getHitCount(boolean reset) { int hc = reset ? removedHitCount.getAndSet(0) : removedHitCount.get(); Iterator it = map.values().iterator(); while (it.hasNext()) { CacheEntry cacheEntry = it.next(); hc += cacheEntry.getHitCount(reset); } return hc; } public ServerCacheOptions getOptions() { synchronized (monitor) { ServerCacheOptions o = new ServerCacheOptions(); o.setMaxIdleSecs(maxIdleSecs); o.setMaxSize(maxSize); o.setMaxSecsToLive(maxSecsToLive); return o; } } public void setOptions(ServerCacheOptions o) { synchronized (monitor) { maxIdleSecs = o.getMaxIdleSecs(); maxSize = o.getMaxSize(); maxSecsToLive = o.getMaxSecsToLive(); } } /** * Return the max cache size. */ public int getMaxSize() { return maxSize; } /** * Set the max cache size. */ public void setMaxSize(int maxSize) { synchronized (monitor) { this.maxSize = maxSize; } } /** * Return the max idle time. */ public long getMaxIdleSecs() { return maxIdleSecs; } /** * Set the max idle time. */ public void setMaxIdleSecs(int maxIdleSecs) { synchronized (monitor) { this.maxIdleSecs = maxIdleSecs; } } /** * Return the maximum time to live. */ public long getMaxSecsToLive() { return maxSecsToLive; } /** * Set the maximum time to live. */ public void setMaxSecsToLive(int maxSecsToLive) { synchronized (monitor) { this.maxSecsToLive = maxSecsToLive; } } /** * Return the name of the cache. */ public String getName() { return name; } /** * Clear the cache. */ public void clear() { map.clear(); } /** * Return a value from the cache. */ public Object get(Object key) { CacheEntry entry = map.get(key); if (entry == null){ missCount.incrementAndGet(); return null; } else { // get value incrementing last // access time and hitCount return entry.getValue(); } } /** * Put a value into the cache. */ public Object put(Object key, Object value) { // put new entry with create time CacheEntry entry = map.put(key, new CacheEntry(key, value)); if (entry == null){ return null; } else { int removedHits = entry.getHitCount(true); removedHitCount.addAndGet(removedHits); return entry.getValue(); } } /** * Put a value into the cache but only if absent. */ public Object putIfAbsent(Object key, Object value) { CacheEntry entry = map.putIfAbsent(key, new CacheEntry(key, value)); if (entry == null){ return null; } else { return entry.getValue(); } } /** * Remove an entry from the cache. */ public Object remove(Object key) { CacheEntry entry = map.remove(key); if (entry == null){ return null; } else { int removedHits = entry.getHitCount(true); removedHitCount.addAndGet(removedHits); return entry.getValue(); } } /** * Return the number of elements in the cache. */ public int size() { return map.size(); } private Iterator cacheEntries() { return map.values().iterator(); } /** * The task used to periodically trim the cache. */ private class TrimTask implements Runnable { public void run() { long startTime = System.currentTimeMillis(); if (logger.isLoggable(Level.FINER)){ logger.finer("trimming cache " + name); } int trimmedByIdle = 0; int trimmedByTTL = 0; int trimmedByLRU = 0; boolean trimMaxSize = maxSize > 0 && maxSize < size(); ArrayList activeList = new ArrayList(); long idleExpire = System.currentTimeMillis() - (maxIdleSecs*1000); long ttlExpire = System.currentTimeMillis() - (maxSecsToLive*1000); Iterator it = cacheEntries(); while (it.hasNext()) { CacheEntry cacheEntry = it.next(); if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) { it.remove(); trimmedByIdle++; } else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) { it.remove(); trimmedByTTL++; } else if (trimMaxSize) { activeList.add(cacheEntry); } } if (trimMaxSize) { trimmedByLRU = activeList.size() - maxSize; if (trimmedByLRU > 0) { // sort into last access time ascending Collections.sort(activeList, comparator); for (int i = maxSize; i < activeList.size(); i++) { // remove if still in the cache map.remove(activeList.get(i).getKey()); } } } long exeTime = System.currentTimeMillis() - startTime; if (logger.isLoggable(Level.FINE)){ logger.fine("Executed trim of cache " + name + " in ["+exeTime +"]millis idle[" + trimmedByIdle + "] timeToLive[" + trimmedByTTL + "] accessTime[" + trimmedByLRU + "]"); } } } /** * Comparator for sorting by last access time. */ private static class CacheEntryComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; public int compare(CacheEntry o1, CacheEntry o2) { return o1.getLastAccessLong().compareTo(o2.getLastAccessLong()); } } /** * Wraps the values to additionally hold createTime and lastAccessTime. */ public static class CacheEntry { private final Object key; private final Object value; private final long createTime; private final AtomicInteger hitCount = new AtomicInteger(); private Long lastAccessTime; public CacheEntry(Object key, Object value) { this.key = key; this.value = value; this.createTime = System.currentTimeMillis(); this.lastAccessTime = Long.valueOf(createTime); } public Object getKey() { return key; } public Object getValue() { // object assignment is atomic hitCount.incrementAndGet(); this.lastAccessTime = Long.valueOf(System.currentTimeMillis()); return value; } public long getCreateTime() { return createTime; } public long getLastAccessTime() { return lastAccessTime.longValue(); } public Long getLastAccessLong() { return lastAccessTime; } public int getHitCount(boolean reset) { if (reset){ return hitCount.getAndSet(0); } else { return hitCount.get(); } } public int getHitCount() { return hitCount.get(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy