/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.ws.core;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Cache object is a hashmap with values wrapped around in soft references. Objects held by the hashmap will
* be cleared when garbage collector has determined it needs to reclaim the memory.
*
* @author tchu
*
*/
public class Cache<T extends Cacheable> {
private static final Logger logger = LoggerFactory.getLogger(Cache.class);
private Map<String, SoftReference<T>> cacheMap =
new HashMap<String, SoftReference<T>>();
private Map<String, Long> timeMap = new HashMap<String, Long>();
private MaintenanceThread maintenanceThread = null;
public Cache() {
logger.debug("In Constructor");
}
/**
* Starts the maintenance thread.
*/
protected synchronized void startMaintenanceThread() {
if (!this.hasMaintenanceThread()) {
this.maintenanceThread = new MaintenanceThread(this, timeMap);
this.maintenanceThread.start();
}
}
/**
* Adds an object to cache. Key for this cache entry is determined by Cacheable.getCacheKey()
* There is no guarantee that this object will be there by the time the next get(key) is called.
*
* @param value
*/
public synchronized void put(T value) {
cacheMap.put(value.getCacheKey(), new SoftReference<T>(value));
}
/**
* Adds an object to cache. There is no guarantee that this object will be there by the time
* the next get(key) is called. If no other references are pointing to the cached object,
* the supplied value are guaranteed to be removed from cache after timeToClearMins + 1 minutes.
*
* @param value
* @param timeToClearMins
*/
public synchronized void put(T value, long timeToClearMins) {
this.put(value);
if (!this.hasMaintenanceThread()) {
startMaintenanceThread();
}
timeMap.put(value.getCacheKey(), new Long(System.currentTimeMillis()
+ (timeToClearMins * 60 * 1000)));
}
/**
* Retrieves an object from cache. If the object was cleared due to memory demand, null will
* be returned.
*
* @param key
* @return value
*/
public T get(String key) {
SoftReference<T> ref = cacheMap.get(key);
if (ref != null) {
if (ref.get() == null) {
logger.debug("Cached item was cleared: " + key);
}
return ref.get(); //maybe still be null
}
return null;
}
/**
* Clears the cache. All keys and values will be eligible for memory collection.
*/
public void clear() {
cacheMap.clear();
timeMap.clear();
}
/**
* Returns true if cache contains this key. Note: Even if this cache contains the key, it
* doesn't mean the value object is still in the cache.
* @param key
* @return true/false
*/
public boolean containsKey(String key) {
return cacheMap.containsKey(key);
}
protected boolean isEmpty() {
return cacheMap.isEmpty();
}
/**
* returns a set of all keys in the cache regardless whether they have been cleared or not.
* @return
*/
public Set<String> keySet() {
return cacheMap.keySet();
}
/**
* Remove an entry from the cache
* @param key
*/
public synchronized void remove(String key) {
cacheMap.remove(key);
timeMap.remove(key);
}
/**
* returns the number of entries in the cache
* @return
*/
public int size() {
return cacheMap.size();
}
/**
* returns true if and only if there is a maintenance thread attached to this cache
* @return true if and only if there is a maintenance thread attached to this cache
*/
protected boolean hasMaintenanceThread() {
return this.maintenanceThread != null;
}
}
/**
* A MaintenanceThread is created when a Cache object is instantiated. The role of this
* thread is to monitor the time limited cache entries and remove them from the cache when
* they expire
*
* @author tchu
*/
class MaintenanceThread extends Thread {
private static final Logger log = LoggerFactory
.getLogger(MaintenanceThread.class);
private Map<String, Long> timeMap = null;
private Cache<? extends Cacheable> cacheObj = null;
private boolean stopRequested = false;
private final long DEFAULT_CHECKING_INTERVAL = 15 * 1000; //check thread every 15 secs
public MaintenanceThread(Cache<? extends Cacheable> cacheObj,
Map<String, Long> timeMap) {
this.cacheObj = cacheObj;
this.timeMap = timeMap;
}
public void requestStop() {
stopRequested = true;
}
@Override
public void run() {
while (!stopRequested) {
if (timeMap.size() > 0) {
Set<String> entriesToRemove = findEntriesToRemove();
if (entriesToRemove.size() > 0) {
Iterator<String> k = entriesToRemove.iterator();
while (k.hasNext()) {
String key = k.next();
synchronized (cacheObj) {
cacheObj.remove(key);
}
}
}
}
try {
sleep(DEFAULT_CHECKING_INTERVAL);
} catch (InterruptedException ie) {
ie.printStackTrace();
log.debug(
"Exception encountered when trying to sleep. Will retry in next sleep cycle.",
ie);
//do nothing
}
}
}
/**
*
* @return Set of entries to remove
*/
private Set<String> findEntriesToRemove() {
Set<String> entriesToRemove = new HashSet<String>();
for (String key : timeMap.keySet()) {
Long expiration = timeMap.get(key);
if (expiration != null) {
//expiration should never be null if set up correctly
if (System.currentTimeMillis() >= expiration.longValue()) {
entriesToRemove.add(key);
}
}
}
return entriesToRemove;
}
}