FormatterCache { @Nullable public byte[] get(Key key, Validator validator, Callable<StoreInfoAndDataLoadResult> loader, boolean writeToStoreInCurrentThread) throws Exception { final Lock readLock = lock.readLock(); StoreInfoAndData cached; try { readLock.lock(); if (!cacheConfig.allowCaching(key)) { return loader.call().data; } cached = memoryCache.getIfPresent(key); boolean invalid = false; if (cached != null && !validator.isCacheVersionValid(cached)) { cached = null; invalid = true; } if (!invalid && cached == null) { cached = loadFromPersistentCache(key, validator); } } finally { readLock.unlock(); } final Lock writeLock = lock.writeLock(); if (cached == null) { try { writeLock.lock(); StoreInfoAndDataLoadResult loaded = loader.call(); cached = loaded; push(key, loaded, writeToStoreInCurrentThread); } finally { writeLock.unlock(); } } return cached.data; } FormatterCache(PersistentStore persistentStore, int memoryCacheSize, int maxStoreRequests);  FormatterCache(PersistentStore persistentStore, int memoryCacheSize, int maxStoreRequests,
                          CacheConfig cacheConfig, ExecutorService executor);  FormatterCache(PersistentStore persistentStore, int memoryCacheSize, int maxStoreRequests, CacheConfig config); @PreDestroy void shutdown(); void remove(Key key); @Nullable byte[] get(Key key, Validator validator, Callable<StoreInfoAndDataLoadResult> loader,
                      boolean writeToStoreInCurrentThread); @Nullable byte[] getPublished(Key key); synchronized void removeAll(int metadataId); void clear();  }
@Test public void testGet() throws Exception { final MemoryPersistentStore persistentStore = new MemoryPersistentStore(); this.formatterCache = new FormatterCache(persistentStore, 1, 5000); final boolean hideWithheld = true; final long changeDate = new Date().getTime(); final Key key = new Key(1, "eng", FormatType.html, "full_view", hideWithheld, FormatterWidth._100); final TestLoader loader = new TestLoader("result", changeDate, false); String result = getAsString(key, changeDate, loader); assertEquals("result", result); StoreInfoAndData info = persistentStore.get(key); assertEquals(changeDate, info.getChangeDate()); assertEquals(false, info.isPublished()); assertEquals("result", info.getDataAsString()); StoreInfo basicInfo = persistentStore.getInfo(key); assertEquals(changeDate, basicInfo.getChangeDate()); assertEquals(false, basicInfo.isPublished()); result = getAsString(key, changeDate, new Callable<StoreInfoAndDataLoadResult>() { @Override public StoreInfoAndDataLoadResult call() throws Exception { throw new AssertionError("Should not be called because cache should be up-to-date"); } }); assertEquals("result", result); final long updatedChangeDate = changeDate + 600; result = getAsString(key, updatedChangeDate, new TestLoader("newVal", updatedChangeDate, false)); assertEquals("newVal", result); info = persistentStore.get(key); assertEquals(updatedChangeDate, info.getChangeDate()); assertEquals(false, info.isPublished()); assertEquals("newVal", info.getDataAsString()); basicInfo = persistentStore.getInfo(key); assertEquals(updatedChangeDate, basicInfo.getChangeDate()); assertEquals(false, basicInfo.isPublished()); }
@Test public void testMemoryCache() throws Exception { final AtomicBoolean persistentStoreHit = new AtomicBoolean(false); this.formatterCache = new FormatterCache(new PersistentStore() { @Override @Nullable public StoreInfoAndData get(@Nonnull Key key) { persistentStoreHit.set(true); return new StoreInfoAndData("lksjdf", 234982734, false); } @Override @Nullable public StoreInfo getInfo(@Nonnull Key key) { return get(key); } @Override public void put(@Nonnull Key key, @Nonnull StoreInfoAndData data) { } @Nullable @Override public byte[] getPublished(@Nonnull Key key) { throw new UnsupportedOperationException("to implement"); } @Override public void remove(@Nonnull Key key) throws IOException, SQLException { } @Override public void setPublished(int metadataId, boolean published) { throw new UnsupportedOperationException("not yet implemented"); } @Override public void clear() { } }, 100, 5000); final boolean hideWithheld = true; final long changeDate = new Date().getTime(); final Key key = new Key(1, "eng", FormatType.html, "full_view", hideWithheld, FormatterWidth._100); formatterCache.get(key, new ChangeDateValidator(changeDate), new TestLoader("result", changeDate, false), true); assertEquals(true, persistentStoreHit.get()); persistentStoreHit.set(false); byte[] bytes = formatterCache.get(key, new ChangeDateValidator(changeDate), new TestLoader("result", changeDate, false), true); String result = new String(bytes, Constants.CHARSET); assertEquals("result", result); assertEquals(false, persistentStoreHit.get()); }
@Test(timeout = 1000L) public void testThreadedPutWorks() throws Exception { final MemoryPersistentStore persistentStore = new MemoryPersistentStore(); final AtomicBoolean waitForStartPut = new AtomicBoolean(false); final AtomicBoolean waitForAllowPut = new AtomicBoolean(false); final AtomicBoolean waitForDone = new AtomicBoolean(false); this.formatterCache = new FormatterCache(persistentStore, 100, 5000) { @Override PersistentStoreRunnable createPersistentStoreRunnable(BlockingQueue<Pair<Key, StoreInfoAndDataLoadResult>> storeRequests, PersistentStore store) { return new PersistentStoreRunnable(storeRequests, store) { @Override void doStore(Pair<Key, StoreInfoAndDataLoadResult> request) throws Exception { waitForStartPut.set(true); while (!waitForAllowPut.get()) { Thread.sleep(100); } super.doStore(request); waitForDone.set(true); } }; } }; final boolean hideWithheld = true; final long changeDate = new Date().getTime(); final Key key = new Key(1, "eng", FormatType.html, "full_view", hideWithheld, FormatterWidth._100); formatterCache.get(key, new ChangeDateValidator(changeDate), new TestLoader("result", changeDate, false), false); waitForStartPut.set(true); assertNull(persistentStore.get(key)); waitForAllowPut.set(true); while (!waitForDone.get()) { Thread.sleep(100); } assertNotNull(persistentStore.get(key)); }