CachingPolicy.java public enum CachingPolicy { THROUGH("through"), AROUND("around"), BEHIND("behind"); private String policy; private CachingPolicy(String policy) { this.policy = policy; } public String getPolicy() { return policy; } }
LruCache.java public class LruCache { class Node { String userId; UserAccount userAccount; Node previous; Node next; public Node(String userId, UserAccount userAccount) { this.userId = userId; this.userAccount = userAccount; } } int capacity; Map<String, Node> cache = new HashMap<>(); Node head; Node end; public LruCache(int capacity) { this.capacity = capacity; } /** * Get user account */ public UserAccount get(String userId) { if (cache.containsKey(userId)) { Node node = cache.get(userId); remove(node); setHead(node); return node.userAccount; } return null; } /** * * Remove node from linked list. */ public void remove(Node node) { if (node.previous != null) { node.previous.next = node.next; } else { head = node.next; } if (node.next != null) { node.next.previous = node.previous; } else { end = node.previous; } } /** * * Move node to the front of the list. */ public void setHead(Node node) { node.next = head; node.previous = null; if (head != null) { head.previous = node; } head = node; if (end == null) { end = head; } } /** * Set user account */ public void set(String userId, UserAccount userAccount) { if (cache.containsKey(userId)) { Node old = cache.get(userId); old.userAccount = userAccount; remove(old); setHead(old); } else { Node newNode = new Node(userId, userAccount); if (cache.size() >= capacity) { System.out.println("# Cache is FULL! Removing " + end.userId + " from cache..."); cache.remove(end.userId); // remove LRU data from cache. remove(end); setHead(newNode); } else { setHead(newNode); } cache.put(userId, newNode); } } public boolean contains(String userId) { return cache.containsKey(userId); } /** * Invalidate cache for user */ public void invalidate(String userId) { System.out.println("# " + userId + " has been updated! Removing older version from cache..."); Node toBeRemoved = cache.get(userId); remove(toBeRemoved); cache.remove(userId); } public boolean isFull() { return cache.size() >= capacity; } public UserAccount getLruData() { return end.userAccount; } /** * Clear cache */ public void clear() { head = null; end = null; cache.clear(); } /** * * Returns cache data in list form. */ public List<UserAccount> getCacheDataInListForm() { ArrayList<UserAccount> listOfCacheData = new ArrayList<>(); Node temp = head; while (temp != null) { listOfCacheData.add(temp.userAccount); temp = temp.next; } return listOfCacheData; } /** * Set cache capacity */ public void setCapacity(int newCapacity) { if (capacity > newCapacity) { clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll // just clear the cache. } else { this.capacity = newCapacity; } } }
CacheStore.java public class CacheStore { static LruCache cache; private CacheStore() { } /** * Init cache capacity */ public static void initCapacity(int capacity) { if (null == cache) { cache = new LruCache(capacity); } else { cache.setCapacity(capacity); } } /** * Get user account using read-through cache */ public static UserAccount readThrough(String userId) { if (cache.contains(userId)) { System.out.println("# Cache Hit!"); return cache.get(userId); } System.out.println("# Cache Miss!"); UserAccount userAccount = DbManager.readFromDb(userId); cache.set(userId, userAccount); return userAccount; } /** * Get user account using write-through cache */ public static void writeThrough(UserAccount userAccount) { if (cache.contains(userAccount.getUserId())) { DbManager.updateDb(userAccount); } else { DbManager.writeToDb(userAccount); } cache.set(userAccount.getUserId(), userAccount); } /** * Get user account using write-around cache */ public static void writeAround(UserAccount userAccount) { if (cache.contains(userAccount.getUserId())) { DbManager.updateDb(userAccount); cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older // version from cache. } else { DbManager.writeToDb(userAccount); } } /** * Get user account using read-through cache with write-back policy */ public static UserAccount readThroughWithWriteBackPolicy(String userId) { if (cache.contains(userId)) { System.out.println("# Cache Hit!"); return cache.get(userId); } System.out.println("# Cache Miss!"); UserAccount userAccount = DbManager.readFromDb(userId); if (cache.isFull()) { System.out.println("# Cache is FULL! Writing LRU data to DB..."); UserAccount toBeWrittenToDb = cache.getLruData(); DbManager.upsertDb(toBeWrittenToDb); } cache.set(userId, userAccount); return userAccount; } /** * Set user account */ public static void writeBehind(UserAccount userAccount) { if (cache.isFull() && !cache.contains(userAccount.getUserId())) { System.out.println("# Cache is FULL! Writing LRU data to DB..."); UserAccount toBeWrittenToDb = cache.getLruData(); DbManager.upsertDb(toBeWrittenToDb); } cache.set(userAccount.getUserId(), userAccount); } /** * Clears cache */ public static void clearCache() { if (null != cache) { cache.clear(); } } /** * Writes remaining content in the cache into the DB. */ public static void flushCache() { System.out.println("# flushCache..."); if (null == cache) { return; } List<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm(); for (UserAccount userAccount : listOfUserAccounts) { DbManager.upsertDb(userAccount); } } /** * Print user accounts */ public static String print() { List<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm(); StringBuilder sb = new StringBuilder(); sb.append("\n--CACHE CONTENT--\n"); for (UserAccount userAccount : listOfUserAccounts) { sb.append(userAccount.toString() + "\n"); } sb.append("----\n"); return sb.toString(); } }