package org.solbase.cache;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import net.rubyeye.xmemcached.exception.MemcachedException;
import org.apache.log4j.Logger;
import org.solbase.lucenehbase.IndexWriter;
public class LayeredCache<K extends Serializable & Comparable<?>, V extends Serializable, Z extends Serializable, M extends Serializable> {
private final static Logger logger = Logger.getLogger(LayeredCache.class);
private static final int BACKGROUND_THREAD_POOL_SIZE = 50;
public static enum ModificationType {
UPDATE, ADD, DELETE
}
private CachedObjectLoader<K, V, Z, M> _loader = null;
private Long cacheTimeout;
private List<VersionedCache<K, V, Z>> caches;
private ThreadPoolExecutor executor = new ThreadPoolExecutor(BACKGROUND_THREAD_POOL_SIZE, BACKGROUND_THREAD_POOL_SIZE, 5, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
private class LockedResult {
ReentrantLock lock = new ReentrantLock();
CachedObjectWrapper<V, Z> result = null;
int refCount = 0;
}
private HashMap<K, LockedResult> currentlyAccesingMap = new HashMap<K, LockedResult>();
public LayeredCache(Long cacheTimeout, List<VersionedCache<K, V, Z>> caches) {
this.caches = caches;
for (VersionedCache<K, V, Z> cache : caches) {
cache.setTimeout(cacheTimeout);
}
this.cacheTimeout = cacheTimeout;
}
public LayeredCache(CachedObjectLoader<K, V, Z, M> loader, Long cacheTimeout, List<VersionedCache<K, V, Z>> caches) {
this(cacheTimeout, caches);
this._loader = loader;
}
public CachedObjectWrapper<V, Z> getCachedObject(K key, String indexName, int start, int end) throws IOException, InterruptedException, MemcachedException, TimeoutException {
return getCachedObject(key, _loader, indexName, start, end);
}
/*
public CachedObjectWrapper<V, Z> getCachedObjectFromCacheOnly(K key, String indexName) throws IOException, InterruptedException, MemcachedException, TimeoutException {
return getCachedObjectFromCacheOnly(key, _loader, indexName);
}
*/
public CachedObjectWrapper<V, Z> getCachedObject(K key, CachedObjectLoader<K, V, Z, M> loader, String indexName, int start, int end) throws IOException, InterruptedException, MemcachedException, TimeoutException {
LockedResult lockedResult;
synchronized (currentlyAccesingMap) {
lockedResult = currentlyAccesingMap.get(key);
if (lockedResult == null) {
lockedResult = new LockedResult();
currentlyAccesingMap.put(key, lockedResult);
}
lockedResult.refCount++;
}
try {
return getObjectRecursively(key, caches.iterator(), loader, System.currentTimeMillis(), indexName, start, end, lockedResult, true);
} finally {
synchronized (currentlyAccesingMap) {
lockedResult.refCount--;
if (lockedResult.refCount == 0) {
currentlyAccesingMap.remove(key);
}
}
}
}
/*
public CachedObjectWrapper<V, Z> getCachedObjectFromCacheOnly(K key, CachedObjectLoader<K, V, Z, M> loader, String indexName) throws IOException, InterruptedException, MemcachedException, TimeoutException {
LockedResult lockedResult;
synchronized (currentlyAccesingMap) {
lockedResult = currentlyAccesingMap.get(key);
if (lockedResult == null) {
lockedResult = new LockedResult();
currentlyAccesingMap.put(key, lockedResult);
}
lockedResult.refCount++;
}
try {
return getObjectRecursively(key, caches.iterator(), loader, System.currentTimeMillis(), indexName, 0, 0, lockedResult, false);
} finally {
synchronized (currentlyAccesingMap) {
lockedResult.refCount--;
if (lockedResult.refCount == 0) {
currentlyAccesingMap.remove(key);
}
}
}
}
*/
public void updateCachedObject(K key, M modificationData, String indexName, IndexWriter writer, ModificationType modType, boolean updateStore, int startDocId, int endDocId) throws IOException, InterruptedException, MemcachedException, TimeoutException {
updateCachedObject(key, modificationData, _loader, indexName, writer, modType, updateStore, startDocId, endDocId);
}
public void updateCachedObject(K key, M modificationData, CachedObjectLoader<K, V, Z, M> loader, String indexName, IndexWriter writer, ModificationType modType, boolean updateStore, int startDocId, int endDocId) throws IOException, InterruptedException, MemcachedException, TimeoutException {
LockedResult lockedResult;
synchronized (currentlyAccesingMap) {
lockedResult = currentlyAccesingMap.get(key);
if (lockedResult == null) {
lockedResult = new LockedResult();
currentlyAccesingMap.put(key, lockedResult);
}
lockedResult.refCount++;
}
try {
modifyObjectRecursively(key, modificationData, caches.iterator(), loader, System.currentTimeMillis(), indexName, lockedResult, writer, modType, updateStore, startDocId, endDocId);
} finally {
synchronized (currentlyAccesingMap) {
lockedResult.refCount--;
if (lockedResult.refCount == 0) {
currentlyAccesingMap.remove(key);
}
}
}
}
private void modifyObjectRecursively(K key, M modificationData, Iterator<VersionedCache<K, V, Z>> cacheIterator, CachedObjectLoader<K, V, Z, M> loader, long currentTime, String indexName, LockedResult lockedResult, IndexWriter writer, ModificationType modType, boolean updateStore, int startDocId, int endDocId) throws IOException {
CachedObjectWrapper<V, Z> tmp = null;
if (cacheIterator.hasNext()) {
VersionedCache<K, V, Z> vc = cacheIterator.next();
tmp = vc.get(key);
if (tmp == null) {
modifyObjectRecursively(key, modificationData, cacheIterator, loader, currentTime, indexName, lockedResult, writer, modType, updateStore, startDocId, endDocId);
} else {
lockedResult.lock.lock();
try {
if (lockedResult.result != null) {
tmp = lockedResult.result;
}
loader.updateObject(tmp, modificationData, this, modType, startDocId, endDocId);
if (updateStore) {
loader.updateObjectStore(key, modificationData, writer, this, modType, startDocId, endDocId);
}
// TODO we aren't using the timeout, so comment out to make
// more performant
// resetCacheTime(key);
} finally {
lockedResult.lock.unlock();
}
// TODO we aren't using the timeout, so comment out to make more
// performant
// resetCacheTime(key);
}
} else {
lockedResult.lock.lock();
try {
if (lockedResult.result != null) {
tmp = lockedResult.result;
loader.updateObject(tmp, modificationData, this, modType, startDocId, endDocId);
}
if (updateStore) {
loader.updateObjectStore(key, modificationData, writer, this, modType, startDocId, endDocId);
}
} finally {
lockedResult.lock.unlock();
}
}
}
private CachedObjectWrapper<V, Z> getObjectRecursively(K key, Iterator<VersionedCache<K, V, Z>> cacheIterator, CachedObjectLoader<K, V, Z, M> loader, long currentTime, String indexName, int start, int end, LockedResult lockedResult, boolean loadFromStore) throws IOException {
CachedObjectWrapper<V, Z> tmp = null;
if (cacheIterator.hasNext()) {
VersionedCache<K, V, Z> vc = cacheIterator.next();
tmp = vc.get(key);
if (tmp == null) {
tmp = getObjectRecursively(key, cacheIterator, loader, currentTime, indexName, start, end, lockedResult, loadFromStore);
if (tmp != null) {
vc.put(key, tmp);
}
} else {
long cacheTime = tmp.getCacheTime();
long diff = currentTime - cacheTime;
if (cacheTime != -1l && diff > cacheTimeout) {
tmp.setCacheTime(-1l);
checkAndReloadCachedObjectAsynch(vc, cacheTime, tmp, key, cacheIterator, loader, currentTime, indexName, start, end, lockedResult, true);
}
}
} else {
if (loadFromStore) {
lockedResult.lock.lock();
try {
if (lockedResult.result == null) {
tmp = loader.loadObject(key, start, end, this);
if(tmp != null){
tmp.setCacheTime(System.currentTimeMillis());
lockedResult.result = tmp;
} else {
logger.debug("empty object loaded for this key: " + key.toString());
}
} else {
tmp = lockedResult.result;
}
} finally {
lockedResult.lock.unlock();
}
}
}
return tmp;
}
private CachedObjectWrapper<V, Z> refreshObject(VersionedCache<K, V, Z> vc, long currentCacheTime, final CachedObjectWrapper<V, Z> objectWrapper, K key, Iterator<VersionedCache<K, V, Z>> cacheIterator, CachedObjectLoader<K, V, Z, M> loader, long currentTime, String indexName, int start, int end, LockedResult lockedResult, boolean loadFromStore) throws IOException {
CachedObjectWrapper<V, Z> tmp = null;
lockedResult.lock.lock();
try {
if (lockedResult.result == null) {
tmp = loader.loadObject(key, start, end, this);
tmp.setCacheTime(System.currentTimeMillis());
lockedResult.result = tmp;
} else {
tmp = lockedResult.result;
}
} finally {
lockedResult.lock.unlock();
}
vc.put(key, tmp);
while (cacheIterator.hasNext()) {
VersionedCache<K, V, Z> tmpVc = cacheIterator.next();
tmpVc.put(key, tmp);
}
//objectWrapper.setCacheTime(currentCacheTime);
return tmp;
}
private void checkAndReloadCachedObjectAsynch(final VersionedCache<K, V, Z> vc, final long currentCacheTime, final CachedObjectWrapper<V, Z> objectWrapper, final K key, final Iterator<VersionedCache<K, V, Z>> cacheIterator, final CachedObjectLoader<K, V, Z, M> loader, final long currentTime, final String indexName, final int start, final int end, final LockedResult lockedResult, final boolean loadFromStore) throws IOException {
Runnable runnable = new Runnable() {
@Override
public void run() {
Z loadedIdentifier;
try {
loadedIdentifier = loader.getVersionIdentifier(key, start, end);
Z versionIdentifier = objectWrapper.getVersionIdentifier();
if (loadedIdentifier == null || versionIdentifier == null || (!versionIdentifier.equals(loadedIdentifier))) {
refreshObject(vc, currentCacheTime, objectWrapper, key, cacheIterator, loader, currentTime, indexName, start, end, lockedResult, loadFromStore);
} else {
resetCacheTime(key);
}
} catch (Throwable e) {
logger.error(e);
}
}
};
try {
executor.execute(runnable);
} catch (RejectedExecutionException ree) {
logger.warn("Async check and reload cached object failed, ignoring ", ree);
}
}
public void resetCacheTime(K key) throws IOException {
for (VersionedCache<K, V, Z> cache : caches) {
cache.resetCacheTime(key, System.currentTimeMillis());
}
}
public boolean isCacheFull() {
VersionedCache<K, V, Z> cache = caches.get(caches.size() - 1);
return cache.isCacheFull();
}
}