package freenet.store;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import freenet.keys.KeyVerifyException;
import freenet.node.stats.StoreAccessStats;
import freenet.node.useralerts.UserAlertManager;
import freenet.support.ByteArrayWrapper;
import freenet.support.LRUMap;
import freenet.support.Logger;
import freenet.support.Ticker;
/**
* LRU in memory store.
*
* For debugging / simulation only
*/
public class RAMFreenetStore<T extends StorableBlock> implements FreenetStore<T> {
private final static class Block {
byte[] header;
byte[] data;
byte[] fullKey;
boolean oldBlock;
}
private final LRUMap<ByteArrayWrapper, Block> blocksByRoutingKey;
private final StoreCallback<T> callback;
private int maxKeys;
private long hits;
private long misses;
private long writes;
public RAMFreenetStore(StoreCallback<T> callback, int maxKeys) {
this.callback = callback;
this.blocksByRoutingKey = LRUMap.createSafeMap(ByteArrayWrapper.FAST_COMPARATOR);
this.maxKeys = maxKeys;
callback.setStore(this);
}
@Override
public synchronized T fetch(byte[] routingKey, byte[] fullKey,
boolean dontPromote, boolean canReadClientCache, boolean canReadSlashdotCache, boolean ignoreOldBlocks, BlockMetadata meta) throws IOException {
ByteArrayWrapper key = new ByteArrayWrapper(routingKey);
Block block = blocksByRoutingKey.get(key);
if(block == null) {
misses++;
return null;
}
if(ignoreOldBlocks && block.oldBlock) {
Logger.normal(this, "Ignoring old block");
return null;
}
try {
T ret =
callback.construct(block.data, block.header, routingKey, block.fullKey, canReadClientCache, canReadSlashdotCache, meta, null);
hits++;
if(!dontPromote)
blocksByRoutingKey.push(key, block);
if(meta != null && block.oldBlock)
meta.setOldBlock();
return ret;
} catch (KeyVerifyException e) {
blocksByRoutingKey.removeKey(key);
misses++;
return null;
}
}
@Override
public synchronized long getMaxKeys() {
return maxKeys;
}
@Override
public synchronized long hits() {
return hits;
}
@Override
public synchronized long keyCount() {
return blocksByRoutingKey.size();
}
@Override
public synchronized long misses() {
return misses;
}
@Override
public synchronized void put(T block, byte[] data, byte[] header, boolean overwrite, boolean isOldBlock) throws KeyCollisionException {
byte[] routingkey = block.getRoutingKey();
byte[] fullKey = block.getFullKey();
writes++;
ByteArrayWrapper key = new ByteArrayWrapper(routingkey);
Block oldBlock = blocksByRoutingKey.get(key);
boolean storeFullKeys = callback.storeFullKeys();
if(oldBlock != null) {
if(callback.collisionPossible()) {
boolean equals = Arrays.equals(oldBlock.data, data) &&
Arrays.equals(oldBlock.header, header) &&
(storeFullKeys ? Arrays.equals(oldBlock.fullKey, fullKey) : true);
if(equals) {
if(!isOldBlock)
oldBlock.oldBlock = false;
return;
}
if(overwrite) {
oldBlock.data = data;
oldBlock.header = header;
if(storeFullKeys)
oldBlock.fullKey = fullKey;
oldBlock.oldBlock = isOldBlock;
} else {
throw new KeyCollisionException();
}
return;
} else {
if(!isOldBlock)
oldBlock.oldBlock = false;
return;
}
}
Block storeBlock = new Block();
storeBlock.data = data;
storeBlock.header = header;
if(storeFullKeys)
storeBlock.fullKey = fullKey;
storeBlock.oldBlock = isOldBlock;
blocksByRoutingKey.push(key, storeBlock);
while(blocksByRoutingKey.size() > maxKeys) {
blocksByRoutingKey.popKey();
}
}
@Override
public synchronized void setMaxKeys(long maxStoreKeys, boolean shrinkNow)
throws IOException {
this.maxKeys = (int)Math.min(Integer.MAX_VALUE, maxStoreKeys);
// Always shrink now regardless of parameter as we will shrink on the next put() anyway.
while(blocksByRoutingKey.size() > maxKeys) {
blocksByRoutingKey.popKey();
}
}
@Override
public long writes() {
return writes;
}
@Override
public long getBloomFalsePositive() {
return -1;
}
@Override
public boolean probablyInStore(byte[] routingKey) {
ByteArrayWrapper key = new ByteArrayWrapper(routingKey);
return blocksByRoutingKey.get(key) != null;
}
public void clear() {
blocksByRoutingKey.clear();
}
public void migrateTo(StoreCallback<T> target, boolean canReadClientCache) throws IOException {
Enumeration<ByteArrayWrapper> keys = blocksByRoutingKey.keys();
while(keys.hasMoreElements()) {
ByteArrayWrapper routingKeyWrapped = keys.nextElement();
byte[] routingKey = routingKeyWrapped.get();
Block block = blocksByRoutingKey.get(routingKeyWrapped);
T ret;
try {
ret = callback.construct(block.data, block.header, routingKey, block.fullKey, canReadClientCache, false, null, null);
} catch (KeyVerifyException e) {
Logger.error(this, "Caught while migrating: "+e, e);
continue;
}
try {
target.getStore().put(ret, block.data, block.header, false, block.oldBlock);
} catch (KeyCollisionException e) {
// Ignore
}
}
}
@Override
public StoreAccessStats getSessionAccessStats() {
return new StoreAccessStats() {
@Override
public long hits() {
return hits;
}
@Override
public long misses() {
return misses;
}
@Override
public long falsePos() {
return 0;
}
@Override
public long writes() {
return writes;
}
};
}
@Override
public StoreAccessStats getTotalAccessStats() {
return null;
}
@Override
public boolean start(Ticker ticker, boolean longStart) throws IOException {
return false;
}
@Override
public void setUserAlertManager(UserAlertManager userAlertManager) {
// Do nothing
}
@Override
public FreenetStore<T> getUnderlyingStore() {
return this;
}
@Override
public void close() {
// Do nothing
}
}