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 } }