package edu.washington.cs.publickey.storage; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import edu.washington.cs.publickey.PublicKeyFriend; public abstract class PersistentStorage { private final static boolean CACHE_ENABLED = true; private final static int CACHE_SIZE = 100000; private final static boolean CACHE_DEBUG = true; private final Cache cache; private static boolean ENABLE_LOGGING = false; public PersistentStorage() { if (CACHE_ENABLED) { cache = new Cache(CACHE_SIZE); } else { cache = null; } } public int addFriends(PublicKeyFriend user, PublicKeyFriend[] friends) throws Exception { int friendsAdded = addFriendsImpl(user, friends); if (friendsAdded > 0 && CACHE_ENABLED) { long time = System.currentTimeMillis(); // expire the user from the cache cache.remove(new PublicKeyHashKey(user.getPublicKeySha1())); // expire all own keys PublicKeyFriend[] ownKeys = getOwnPublicKeys(user); cache.remove(ownKeys); // get all friends and expire them as well PublicKeyFriend[] dbFriends = getFriendPublicKeys(user); cache.remove(dbFriends); log("keeping cache up to date, overhead: " + (System.currentTimeMillis() - time) + "ms"); } return friendsAdded; } private void log(String msg) { if (ENABLE_LOGGING) { System.out.println(msg); } } protected abstract int addFriendsImpl(PublicKeyFriend user, PublicKeyFriend[] friends) throws Exception; public void addPublicKey(PublicKeyFriend key) throws Exception { boolean dbModified = addPublicKeyImpl(key); if (dbModified && CACHE_ENABLED) { long time = System.currentTimeMillis(); // expire the user from the cache cache.remove(new PublicKeyHashKey(key.getPublicKeySha1())); // get all friends and expire them as well PublicKeyFriend[] dbFriends = getFriendPublicKeys(key); cache.remove(dbFriends); log("keeping cache up to date, overhead: " + (System.currentTimeMillis() - time) + "ms"); } } protected abstract boolean addPublicKeyImpl(PublicKeyFriend key) throws Exception; public PublicKeyFriend[] getFriendPublicKeys(PublicKeyFriend friend) throws Exception { return getFriendPublicKeysImpl(friend); } protected abstract PublicKeyFriend[] getFriendPublicKeysImpl(PublicKeyFriend friend) throws Exception; public List<PublicKeyFriend> getFriendsUsingPublicKey(PublicKeyFriend f) throws Exception { if (!CACHE_ENABLED) { return getFriendsUsingPublicKeyImpl(f.getPublicKeySha1()); } else { List<PublicKeyFriend> cachedEntries = cache.getFriendsUsingPublicKey(f); if (CACHE_DEBUG) { List<PublicKeyFriend> dbEntries = getFriendsUsingPublicKeyImpl(f.getPublicKeySha1()); boolean same = sameResult(dbEntries, cachedEntries); if (!same) { System.err.println("cache inconsistent, clearing"); cache.clear(); } return dbEntries; } return cachedEntries; } } private static boolean sameResult(List<PublicKeyFriend> dbEntries, List<PublicKeyFriend> cachedEntries) { if (cachedEntries.size() != dbEntries.size()) { System.err.println("Cache error!!!, cached=" + cachedEntries.size() + " db=" + dbEntries.size()); return false; } HashMap<PublicKeyHashKey, PublicKeyFriend> cacheMap = new HashMap<PublicKeyHashKey, PublicKeyFriend>(); HashMap<PublicKeyHashKey, PublicKeyFriend> dbMap = new HashMap<PublicKeyHashKey, PublicKeyFriend>(); for (PublicKeyFriend publicKeyFriend : cachedEntries) { cacheMap.put(new PublicKeyHashKey(publicKeyFriend.getPublicKeySha1()), publicKeyFriend); } for (PublicKeyFriend dbFriend : dbEntries) { PublicKeyHashKey dbKey = new PublicKeyHashKey(dbFriend.getPublicKeySha1()); dbMap.put(dbKey, dbFriend); if (!cacheMap.containsKey(dbKey)) { System.err.println("Cache error!!!, cache does not contain: " + dbFriend.getKeyNick()); return false; } } for (PublicKeyFriend cacheFriend : cachedEntries) { PublicKeyHashKey cacheKey = new PublicKeyHashKey(cacheFriend.getPublicKeySha1()); cacheMap.put(cacheKey, cacheFriend); if (!dbMap.containsKey(cacheKey)) { System.err.println("Cache error!!!, db does not contain: " + cacheFriend.getKeyNick()); return false; } } return true; } protected abstract List<PublicKeyFriend> getFriendsUsingPublicKeyImpl(final byte[] publickeysha1) throws Exception; public PublicKeyFriend[] getOwnPublicKeys(PublicKeyFriend user) throws Exception { return getOwnPublicKeysImpl(user); } protected abstract PublicKeyFriend[] getOwnPublicKeysImpl(PublicKeyFriend user) throws Exception; public abstract void shutdown(); public abstract void updateUserLastSeen(PublicKeyFriend user) throws Exception; public abstract int getDbQueueLength(); public abstract void deleteExpiredKeys(); class Cache { private final CacheMap cache; private long cacheHits; private long totalLookups; public Cache(final int maxEntries) { cache = new CacheMap(maxEntries); } public void remove(PublicKeyFriend[] dbFriends) { for (PublicKeyFriend f : dbFriends) { remove(new PublicKeyHashKey(f.getPublicKeySha1())); } } public void clear() { System.err.println("clearing cache"); cache.clear(); } public List<PublicKeyFriend> getFriendsUsingPublicKey(PublicKeyFriend f) throws Exception { totalLookups++; PublicKeyHashKey k = new PublicKeyHashKey(f.getPublicKeySha1()); List<PublicKeyFriend> v = cache.get(k); if (v != null) { cacheHits++; System.out.println("Serving from $$, cache hit rate=" + Math.round(((100.0) * cacheHits) / totalLookups) + "%, size=" + cache.size()); return v; } else { List<PublicKeyFriend> dbValue = getFriendsUsingPublicKeyImpl(f.getPublicKeySha1()); cache.put(k, new PublicKeyCacheValue(dbValue)); System.out.println("Serving from db, cache hit rate=" + Math.round(((100.0) * cacheHits) / totalLookups) + "%, size=" + cache.size()); return dbValue; } } public void remove(PublicKeyHashKey k) { cache.remove(k); } } private static class CacheMap { /** * */ private static final long serialVersionUID = 1L; private final Map<PublicKeyHashKey, PublicKeyCacheValue> cache = new LinkedHashMap<PublicKeyHashKey, PublicKeyCacheValue>() { private static final long serialVersionUID = 1L; protected boolean removeEldestEntry(Map.Entry<PublicKeyHashKey, PublicKeyCacheValue> eldest) { boolean atCapacity = size() > maxEntries; if (atCapacity) { remove(eldest.getKey()); } return atCapacity; } }; private final long maxEntries; public CacheMap(int maxEntries) { this.maxEntries = maxEntries; } public int size() { return cache.size(); } public void clear() { cache.clear(); } public synchronized List<PublicKeyFriend> get(PublicKeyHashKey key) { PublicKeyCacheValue v = cache.get(key); if (v != null) { return v.cachedValue; } else { return null; } } public synchronized void put(PublicKeyHashKey k, PublicKeyCacheValue v) { cache.put(k, v); } public synchronized PublicKeyCacheValue remove(PublicKeyHashKey key) { return cache.remove(key); } } // private static class NetIDHashKey { // final int hashcode; // final byte[] netuidsha; // // public NetIDHashKey(byte[] netuidsha) { // this.netuidsha = netuidsha; // this.hashcode = Arrays.hashCode(netuidsha); // } // // public boolean equals(Object o) { // if (o instanceof NetIDHashKey) { // NetIDHashKey c = (NetIDHashKey) o; // if (Arrays.equals(c.netuidsha, netuidsha)) { // return true; // } // } // return false; // } // // public int hashCode() { // return hashcode; // } // } private static class PublicKeyCacheValue { List<PublicKeyFriend> cachedValue; long lastSeen; public PublicKeyCacheValue(List<PublicKeyFriend> v) { this.cachedValue = v; this.lastSeen = System.currentTimeMillis(); } } private static class PublicKeyHashKey { final int hashcode; final byte[] publickey; public PublicKeyHashKey(byte[] publickeysha) { this.publickey = publickeysha; this.hashcode = Arrays.hashCode(publickey); } public boolean equals(Object o) { if (o instanceof PublicKeyHashKey) { PublicKeyHashKey c = (PublicKeyHashKey) o; if (Arrays.equals(c.publickey, publickey)) { return true; } } return false; } public int hashCode() { return hashcode; } } }