package com.subgraph.orchid.directory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.subgraph.orchid.Descriptor;
import com.subgraph.orchid.DirectoryStore;
import com.subgraph.orchid.DirectoryStore.CacheFile;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.directory.parsing.DocumentParser;
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
import com.subgraph.orchid.misc.GuardedBy;
public abstract class DescriptorCache <T extends Descriptor> {
private final static Logger logger = Logger.getLogger(DescriptorCache.class.getName());
private final DescriptorCacheData<T> data;
private final DirectoryStore store;
private final ScheduledExecutorService rebuildExecutor =
Threading.newScheduledPool("DescriptorCache rebuild worker");
private final CacheFile cacheFile;
private final CacheFile journalFile;
@GuardedBy("this")
private int droppedBytes;
@GuardedBy("this")
private int journalLength;
@GuardedBy("this")
private int cacheLength;
@GuardedBy("this")
private boolean initiallyLoaded;
DescriptorCache(DirectoryStore store, CacheFile cacheFile, CacheFile journalFile) {
this.data = new DescriptorCacheData<T>();
this.store = store;
this.cacheFile = cacheFile;
this.journalFile = journalFile;
startRebuildTask();
}
public synchronized void initialLoad() {
if(initiallyLoaded) {
return;
}
reloadCache();
}
public void shutdown() {
rebuildExecutor.shutdownNow();
}
public T getDescriptor(HexDigest digest) {
return data.findByDigest(digest);
}
public synchronized void addDescriptors(List<T> descriptors) {
final List<T> journalDescriptors = new ArrayList<T>();
int duplicateCount = 0;
for(T d: descriptors) {
if(data.addDescriptor(d)) {
if(d.getCacheLocation() == Descriptor.CacheLocation.NOT_CACHED) {
journalLength += d.getBodyLength();
journalDescriptors.add(d);
}
} else {
duplicateCount += 1;
}
}
if(!journalDescriptors.isEmpty()) {
store.appendDocumentList(journalFile, journalDescriptors);
}
if(duplicateCount > 0) {
logger.info("Duplicate descriptors added to journal, count = "+ duplicateCount);
}
}
public void addDescriptor(T d) {
final List<T> descriptors = new ArrayList<T>();
descriptors.add(d);
addDescriptors(descriptors);
}
private synchronized void clearMemoryCache() {
data.clear();
journalLength = 0;
cacheLength = 0;
droppedBytes = 0;
}
private synchronized void reloadCache() {
clearMemoryCache();
final ByteBuffer[] buffers = loadCacheBuffers();
loadCacheFileBuffer(buffers[0]);
loadJournalFileBuffer(buffers[1]);
if(!initiallyLoaded) {
initiallyLoaded = true;
}
}
private ByteBuffer[] loadCacheBuffers() {
synchronized (store) {
final ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = store.loadCacheFile(cacheFile);
buffers[1] = store.loadCacheFile(journalFile);
return buffers;
}
}
private void loadCacheFileBuffer(ByteBuffer buffer) {
cacheLength = buffer.limit();
if(cacheLength == 0) {
return;
}
final DocumentParser<T> parser = createDocumentParser(buffer);
final DocumentParsingResult<T> result = parser.parse();
if(result.isOkay()) {
for(T d: result.getParsedDocuments()) {
d.setCacheLocation(Descriptor.CacheLocation.CACHED_CACHEFILE);
data.addDescriptor(d);
}
}
}
private void loadJournalFileBuffer(ByteBuffer buffer) {
journalLength = buffer.limit();
if(journalLength == 0) {
return;
}
final DocumentParser<T> parser = createDocumentParser(buffer);
final DocumentParsingResult<T> result = parser.parse();
if(result.isOkay()) {
int duplicateCount = 0;
logger.fine("Loaded "+ result.getParsedDocuments().size() + " descriptors from journal");
for(T d: result.getParsedDocuments()) {
d.setCacheLocation(Descriptor.CacheLocation.CACHED_JOURNAL);
if(!data.addDescriptor(d)) {
duplicateCount += 1;
}
}
if(duplicateCount > 0) {
logger.info("Found "+ duplicateCount + " duplicate descriptors in journal file");
}
} else if(result.isInvalid()) {
logger.warning("Invalid descriptor data parsing from journal file : "+ result.getMessage());
} else if(result.isError()) {
logger.warning("Error parsing descriptors from journal file : "+ result.getMessage());
}
}
abstract protected DocumentParser<T> createDocumentParser(ByteBuffer buffer);
private ScheduledFuture<?> startRebuildTask() {
return rebuildExecutor.scheduleAtFixedRate(new Runnable() {
public void run() {
maybeRebuildCache();
}
}, 5, 30, TimeUnit.MINUTES);
}
private synchronized void maybeRebuildCache() {
if(!initiallyLoaded) {
return;
}
droppedBytes += data.cleanExpired();
if(!shouldRebuildCache()) {
return;
}
rebuildCache();
}
private boolean shouldRebuildCache() {
if(journalLength < 16384) {
return false;
}
if(droppedBytes > (journalLength + cacheLength) / 3) {
return true;
}
if(journalLength > (cacheLength / 2)) {
return true;
}
return false;
}
private void rebuildCache() {
synchronized(store) {
store.writeDocumentList(cacheFile, data.getAllDescriptors());
store.removeCacheFile(journalFile);
}
reloadCache();
}
}