/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.storage;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.utilities.TimestampedCacheEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DOReader Cache to be used by DOManager to make object retrieval more
* efficient
*
* @author Frank Asseg
* @author Benjamin Armintor
*
*/
public class DOReaderCache extends TimerTask {
private static final Logger LOG = LoggerFactory
.getLogger(DOReaderCache.class);
private int maxSeconds;
// default the max entries to default initial size of the map
private int maxEntries = 16;
// since we need to synchronize access, we might as well
// gain the utility of a linked map
private final Map<String, TimestampedCacheEntry<DOReader>> cacheMap =
new FiniteLinkedMap<String, TimestampedCacheEntry<DOReader>>();
private final ReentrantLock mapLock = new ReentrantLock();
/**
* create a new {@link DOReaderCache} instance
*/
public DOReaderCache() {
super();
LOG.debug("{} initialized",DOReaderCache.class.getName());
}
/**
* set the maximal time in seconds an object should live in the cache
*
* @param maxSeconds
* the seconds objects will live in the cache before expiring
*/
public void setMaxSeconds(int maxSeconds) {
this.maxSeconds = maxSeconds;
}
/**
* set the max number of entries the cache can hold
*
* @param maxEntries
* the number of entries
*/
public void setMaxEntries(int maxEntries) {
this.maxEntries = maxEntries;
}
/**
* add a new entry to the cache
*
* @param reader
* the {@link DOReader} to be cached
*/
public final void put(final DOReader reader) {
put(reader, System.currentTimeMillis());
}
public final void put(final DOReader reader, long cacheTime) {
try {
String pid = reader.GetObjectPID();
LOG.debug("adding {} to cache", pid);
mapLock.lock();
cacheMap.put(pid,
new TimestampedCacheEntry<DOReader>(cacheTime, reader));
mapLock.unlock();
} catch (ServerException e) {
throw new RuntimeException(
"Unable to retrieve PID from reader for caching");
}
}
/**
* remove an entry from the cache
*
* @param pid
* the entry's pid
*/
public final void remove(final String pid) {
mapLock.lock();
cacheMap.remove(pid);
mapLock.unlock();
}
/**
* get an {@link DOReader} from the cache
*
* @param pid
* the pid of the {@link DOReader}
* @return the corresponding {@link DOReader} or null if there is no
* applicable cache content
*/
public final DOReader get(final String pid) {
DOReader result = null;
mapLock.lock();
if (cacheMap.containsKey(pid)) {
TimestampedCacheEntry<DOReader> e = cacheMap.remove(pid);
LOG.debug("cache hit for {}", pid);
result = e.value();
// put the cache hit at the end of the FIFO list
cacheMap.put(pid, e.refresh());
} else {
LOG.debug("cache miss for {}", pid);
}
mapLock.unlock();
return result;
}
/**
* {@link TimerTask} implementation to be used a managed Thread by the
* spring framework
*/
@Override
public void run() {
this.removeExpired();
}
/**
* remove expired entries from the cache
*/
public final void removeExpired() {
mapLock.lock();
Iterator<Entry<String, TimestampedCacheEntry<DOReader>>> entries = cacheMap.entrySet().iterator();
if (entries.hasNext()) {
long now = System.currentTimeMillis();
while (entries.hasNext()) {
Entry<String, TimestampedCacheEntry<DOReader>> entry = entries.next();
TimestampedCacheEntry<DOReader> e = entry.getValue();
long age = e.ageAt(now);
if (age > (maxSeconds * 1000)) {
entries.remove();
String pid = entry.getKey();
LOG.debug("removing entry {} after {} milliseconds",
pid, age);
}
}
}
mapLock.unlock();
}
@SuppressWarnings("serial")
private class FiniteLinkedMap<K, V> extends LinkedHashMap<K, V> {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return this.size() > maxEntries;
}
}
}