void storeVariantEntry( final HttpHost target, final HttpRequest req, final HttpCacheEntry entry) throws IOException { final String parentURI = uriExtractor.getURI(target, req); final String variantURI = uriExtractor.getVariantURI(target, req, entry); storage.putEntry(variantURI, entry); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry existing) throws IOException { return doGetUpdatedParentEntry( req.getRequestLine().getUri(), existing, entry, uriExtractor.getVariantKey(req, entry), variantURI); } }; try { storage.updateEntry(parentURI, callback); } catch (final HttpCacheUpdateException e) { log.warn("Could not update key [" + parentURI + "]", e); } }
@Override public void reuseVariantEntryFor(final HttpHost target, final HttpRequest req, final Variant variant) throws IOException { final String parentCacheKey = uriExtractor.getURI(target, req); final HttpCacheEntry entry = variant.getEntry(); final String variantKey = uriExtractor.getVariantKey(req, entry); final String variantCacheKey = variant.getCacheKey(); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry existing) throws IOException { return doGetUpdatedParentEntry(req.getRequestLine().getUri(), existing, entry, variantKey, variantCacheKey); } }; try { storage.updateEntry(parentCacheKey, callback); } catch (final HttpCacheUpdateException e) { log.warn("Could not update key [" + parentCacheKey + "]", e); } }
@Override public void updateEntry( final String url, final HttpCacheUpdateCallback callback) throws IOException { Args.notNull(url, "URL"); Args.notNull(callback, "Callback"); ensureValidState(); synchronized (this) { final HttpCacheEntry existing = this.entries.get(url); final HttpCacheEntry updated = callback.update(existing); this.entries.put(url, updated); if (existing != updated) { keepResourceReference(updated); } } }
@Override public void updateEntry(final String url, final HttpCacheUpdateCallback callback) throws IOException { synchronized (this) { final HttpCacheEntry existing = loadCacheEntry(url); final HttpCacheEntry updated = callback.update(existing); saveCacheEntry(url, updated); } }
@Override public synchronized void updateEntry(final String key, final HttpCacheUpdateCallback callback) throws IOException, HttpCacheUpdateException { int numRetries = 0; do{ final Element oldElement = cache.get(key); HttpCacheEntry existingEntry = null; if(oldElement != null){ final byte[] data = (byte[])oldElement.getValue(); existingEntry = serializer.readFrom(new ByteArrayInputStream(data)); } final HttpCacheEntry updatedEntry = callback.update(existingEntry); if (existingEntry == null) { putEntry(key, updatedEntry); return; } else { // Attempt to do a CAS replace, if we fail then retry // While this operation should work fine within this instance, multiple instances // could trample each others' data final ByteArrayOutputStream bos = new ByteArrayOutputStream(); serializer.writeTo(updatedEntry, bos); final Element newElement = new Element(key, bos.toByteArray()); if (cache.replace(oldElement, newElement)) { return; }else{ numRetries++; } } }while(numRetries <= maxUpdateRetries); throw new HttpCacheUpdateException("Failed to update"); }
@Override public synchronized void updateEntry( final String url, final HttpCacheUpdateCallback callback) throws IOException { final HttpCacheEntry existingEntry = entries.get(url); entries.put(url, callback.update(existingEntry)); }
@Test public void testCacheUpdateCanUpdateNullEntry() throws IOException, HttpCacheUpdateException { final String url = "foo"; final String key = "key"; final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final byte[] serialized = HttpTestUtils.getRandomBytes(128); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { assertNull(old); return updatedValue; } }; // get empty old entry when(mockKeyHashingScheme.hash(url)).thenReturn(key); when(mockMemcachedClient.gets(key)).thenReturn(null); when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue)) .thenReturn(mockMemcachedCacheEntry); when(mockMemcachedCacheEntry.toByteArray()).thenReturn(serialized); when( mockMemcachedClient.set(key, 0, serialized)).thenReturn(null); impl.updateEntry(url, callback); verify(mockKeyHashingScheme, times(2)).hash(url); verify(mockMemcachedClient).gets(key); verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue); verify(mockMemcachedCacheEntry).toByteArray(); verify(mockMemcachedClient).set(key, 0, serialized); }
@Test public void testUpdateThrowsIOExceptionIfMemcachedTimesOut() throws HttpCacheUpdateException { final String url = "foo"; final String key = "key"; final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { assertNull(old); return updatedValue; } }; // get empty old entry when(mockKeyHashingScheme.hash(url)).thenReturn(key); when(mockMemcachedClient.gets(key)) .thenThrow(new OperationTimeoutException("")); try { impl.updateEntry(url, callback); fail("should have thrown exception"); } catch (final IOException expected) { } verify(mockKeyHashingScheme).hash(url); verify(mockMemcachedClient).gets(key); }
@Test public void testCacheUpdateNullEntry() throws IOException, HttpCacheUpdateException { final String key = "foo"; final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final Element element = new Element(key, new byte[]{}); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){ @Override public HttpCacheEntry update(final HttpCacheEntry old){ assertNull(old); return updatedValue; } }; // get empty old entry when(mockCache.get(key)).thenReturn(null); // put new entry mockSerializer.writeTo(same(updatedValue), isA(OutputStream.class)); impl.updateEntry(key, callback); verify(mockCache).get(key); verify(mockSerializer).writeTo(same(updatedValue), isA(OutputStream.class)); verify(mockCache).put(element); }
@Test public void testCacheUpdate() throws IOException, HttpCacheUpdateException { final String key = "foo"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final Element existingElement = new Element(key, new byte[]{}); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){ @Override public HttpCacheEntry update(final HttpCacheEntry old){ assertEquals(existingValue, old); return updatedValue; } }; // get existing old entry when(mockCache.get(key)).thenReturn(existingElement); when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue); // update mockSerializer.writeTo(same(updatedValue), isA(OutputStream.class)); when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(true); impl.updateEntry(key, callback); verify(mockCache).get(key); verify(mockSerializer).readFrom(isA(InputStream.class)); verify(mockSerializer).writeTo(same(updatedValue), isA(OutputStream.class)); verify(mockCache).replace(same(existingElement), isA(Element.class)); }
@Test public void testSingleCacheUpdateRetry() throws IOException, HttpCacheUpdateException { final String key = "foo"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final Element existingElement = new Element(key, new byte[]{}); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){ @Override public HttpCacheEntry update(final HttpCacheEntry old){ assertEquals(existingValue, old); return updatedValue; } }; // get existing old entry, will happen twice when(mockCache.get(key)).thenReturn(existingElement); when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue); // Fail first and then succeed when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(false).thenReturn(true); impl.updateEntry(key, callback); verify(mockCache, times(2)).get(key); verify(mockSerializer, times(2)).readFrom(isA(InputStream.class)); verify(mockSerializer, times(2)).writeTo(same(updatedValue), isA(OutputStream.class)); verify(mockCache, times(2)).replace(same(existingElement), isA(Element.class)); }
@Test public void testCacheUpdateFail() throws IOException { final String key = "foo"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final Element existingElement = new Element(key, new byte[]{}); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){ @Override public HttpCacheEntry update(final HttpCacheEntry old){ assertEquals(existingValue, old); return updatedValue; } }; // get existing old entry when(mockCache.get(key)).thenReturn(existingElement); when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue); // update but fail when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(false); try{ impl.updateEntry(key, callback); fail("Expected HttpCacheUpdateException"); } catch (final HttpCacheUpdateException e) { } verify(mockCache, times(2)).get(key); verify(mockSerializer, times(2)).readFrom(isA(InputStream.class)); verify(mockSerializer, times(2)).writeTo(same(updatedValue), isA(OutputStream.class)); verify(mockCache, times(2)).replace(same(existingElement), isA(Element.class)); }
@Override public void updateEntry(final String key, final HttpCacheUpdateCallback callback) throws IOException { final HttpCacheEntry v1 = map.get(key); final HttpCacheEntry v2 = callback.update(v1); map.put(key,v2); }
@Override public void updateEntry(String key, HttpCacheUpdateCallback callback) throws IOException, HttpCacheUpdateException { HttpCacheEntry existing = getEntry(key); HttpCacheEntry updated = callback.update(existing); if(updated == null ){ removeEntry(key); }else{ putEntry(key, updated); } }
@Override public void updateEntry(final String url, final HttpCacheUpdateCallback callback) throws HttpCacheUpdateException, IOException { int numRetries = 0; final String key = getCacheKey(url); if (key == null) { throw new HttpCacheUpdateException("couldn't generate cache key"); } do { try { final CASValue<Object> v = client.gets(key); MemcachedCacheEntry mce = (v == null) ? null : reconstituteEntry(v.getValue()); if (mce != null && (!url.equals(mce.getStorageKey()))) { mce = null; } final HttpCacheEntry existingEntry = (mce == null) ? null : mce.getHttpCacheEntry(); final HttpCacheEntry updatedEntry = callback.update(existingEntry); if (existingEntry == null) { putEntry(url, updatedEntry); return; } else { final byte[] updatedBytes = serializeEntry(url, updatedEntry); final CASResponse casResult = client.cas(key, v.getCas(), updatedBytes); if (casResult != CASResponse.OK) { numRetries++; } else { return; } } } catch (final OperationTimeoutException ex) { throw new MemcachedOperationTimeoutException(ex); } } while (numRetries <= maxUpdateRetries); throw new HttpCacheUpdateException("Failed to update"); }
@Test public void testCacheUpdateOverwritesNonMatchingHashCollision() throws IOException, HttpCacheUpdateException { final String url = "foo"; final String key = "key"; final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final byte[] oldBytes = HttpTestUtils.getRandomBytes(128); final CASValue<Object> casValue = new CASValue<Object>(-1, oldBytes); final byte[] newBytes = HttpTestUtils.getRandomBytes(128); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { assertNull(old); return updatedValue; } }; // get empty old entry when(mockKeyHashingScheme.hash(url)).thenReturn(key); when(mockMemcachedClient.gets(key)).thenReturn(casValue); when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry()) .thenReturn(mockMemcachedCacheEntry); when(mockMemcachedCacheEntry.getStorageKey()).thenReturn("not" + url); when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue)) .thenReturn(mockMemcachedCacheEntry2); when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes); when( mockMemcachedClient.set(key, 0, newBytes)).thenReturn(null); impl.updateEntry(url, callback); verify(mockKeyHashingScheme, times(2)).hash(url); verify(mockMemcachedClient).gets(key); verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry(); verify(mockMemcachedCacheEntry).getStorageKey(); verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue); verify(mockMemcachedCacheEntry2).toByteArray(); verify(mockMemcachedClient).set(key, 0, newBytes); }
@Test public void testCacheUpdateCanUpdateExistingEntry() throws IOException, HttpCacheUpdateException { final String url = "foo"; final String key = "key"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final byte[] oldBytes = HttpTestUtils.getRandomBytes(128); final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes); final byte[] newBytes = HttpTestUtils.getRandomBytes(128); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { assertSame(existingValue, old); return updatedValue; } }; // get empty old entry when(mockKeyHashingScheme.hash(url)).thenReturn(key); when(mockMemcachedClient.gets(key)).thenReturn(casValue); when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry()) .thenReturn(mockMemcachedCacheEntry); when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url); when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue); when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue)) .thenReturn(mockMemcachedCacheEntry2); when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes); when( mockMemcachedClient.cas(key, casValue.getCas(), newBytes)).thenReturn(CASResponse.OK); impl.updateEntry(url, callback); verify(mockKeyHashingScheme).hash(url); verify(mockMemcachedClient).gets(key); verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry(); verify(mockMemcachedCacheEntry).getStorageKey(); verify(mockMemcachedCacheEntry).getHttpCacheEntry(); verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue); verify(mockMemcachedCacheEntry2).toByteArray(); verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes); }
@Test public void testCacheUpdateThrowsExceptionsIfCASFailsEnoughTimes() throws IOException { final String url = "foo"; final String key = "key"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final byte[] oldBytes = HttpTestUtils.getRandomBytes(128); final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes); final byte[] newBytes = HttpTestUtils.getRandomBytes(128); final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(0).build(); impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config, mockMemcachedCacheEntryFactory, mockKeyHashingScheme); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { assertSame(existingValue, old); return updatedValue; } }; // get empty old entry when(mockKeyHashingScheme.hash(url)).thenReturn(key); when(mockMemcachedClient.gets(key)).thenReturn(casValue); when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry()) .thenReturn(mockMemcachedCacheEntry); when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url); when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue); when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue)) .thenReturn(mockMemcachedCacheEntry2); when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes); when( mockMemcachedClient.cas(key, casValue.getCas(), newBytes)).thenReturn(CASResponse.EXISTS); try { impl.updateEntry(url, callback); fail("should have thrown exception"); } catch (final HttpCacheUpdateException expected) { } verify(mockKeyHashingScheme).hash(url); verify(mockMemcachedClient).gets(key); verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry(); verify(mockMemcachedCacheEntry).getStorageKey(); verify(mockMemcachedCacheEntry).getHttpCacheEntry(); verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue); verify(mockMemcachedCacheEntry2).toByteArray(); verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes); }
@Test public void testCacheUpdateCanUpdateExistingEntryWithRetry() throws IOException, HttpCacheUpdateException { final String url = "foo"; final String key = "key"; final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry existingValue2 = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry updatedValue2 = HttpTestUtils.makeCacheEntry(); final byte[] oldBytes2 = HttpTestUtils.getRandomBytes(128); final CASValue<Object> casValue2 = new CASValue<Object>(2, oldBytes2); final byte[] newBytes2 = HttpTestUtils.getRandomBytes(128); final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { @Override public HttpCacheEntry update(final HttpCacheEntry old) { if (old == existingValue) { return updatedValue; } assertSame(existingValue2, old); return updatedValue2; } }; when(mockKeyHashingScheme.hash(url)).thenReturn(key); // take two when(mockMemcachedClient.gets(key)).thenReturn(casValue2); when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry()) .thenReturn(mockMemcachedCacheEntry3); when(mockMemcachedCacheEntry3.getStorageKey()).thenReturn(url); when(mockMemcachedCacheEntry3.getHttpCacheEntry()).thenReturn(existingValue2); when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue2)) .thenReturn(mockMemcachedCacheEntry4); when(mockMemcachedCacheEntry4.toByteArray()).thenReturn(newBytes2); when( mockMemcachedClient.cas(key, casValue2.getCas(), newBytes2)).thenReturn(CASResponse.OK); impl.updateEntry(url, callback); verify(mockKeyHashingScheme).hash(url); verify(mockMemcachedClient).gets(key); verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry(); verify(mockMemcachedCacheEntry3).set(oldBytes2); verify(mockMemcachedCacheEntry3).getStorageKey(); verify(mockMemcachedCacheEntry3).getHttpCacheEntry(); verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue2); verify(mockMemcachedCacheEntry4).toByteArray(); verify(mockMemcachedClient).cas(key, casValue2.getCas(), newBytes2); verifyNoMoreInteractions(mockMemcachedClient); verifyNoMoreInteractions(mockKeyHashingScheme); verifyNoMoreInteractions(mockMemcachedCacheEntry); verifyNoMoreInteractions(mockMemcachedCacheEntry2); verifyNoMoreInteractions(mockMemcachedCacheEntry3); verifyNoMoreInteractions(mockMemcachedCacheEntry4); verifyNoMoreInteractions(mockMemcachedCacheEntryFactory); }