View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.cache;
28  
29  
30  import static org.junit.jupiter.api.Assertions.assertEquals;
31  import static org.junit.jupiter.api.Assertions.assertFalse;
32  import static org.junit.jupiter.api.Assertions.assertNotNull;
33  import static org.junit.jupiter.api.Assertions.assertNull;
34  import static org.junit.jupiter.api.Assertions.assertSame;
35  import static org.mockito.Mockito.verify;
36  import static org.mockito.Mockito.verifyNoMoreInteractions;
37  
38  import java.net.URI;
39  import java.time.Instant;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  import java.util.stream.Collectors;
45  
46  import org.apache.hc.client5.http.HeadersMatcher;
47  import org.apache.hc.client5.http.cache.HttpCacheEntry;
48  import org.apache.hc.client5.http.classic.methods.HttpGet;
49  import org.apache.hc.client5.http.utils.DateUtils;
50  import org.apache.hc.core5.http.HttpHeaders;
51  import org.apache.hc.core5.http.HttpHost;
52  import org.apache.hc.core5.http.HttpRequest;
53  import org.apache.hc.core5.http.HttpResponse;
54  import org.apache.hc.core5.http.HttpStatus;
55  import org.apache.hc.core5.http.message.BasicHeader;
56  import org.apache.hc.core5.http.message.BasicHttpRequest;
57  import org.apache.hc.core5.http.message.BasicHttpResponse;
58  import org.apache.hc.core5.net.URIBuilder;
59  import org.apache.hc.core5.util.ByteArrayBuffer;
60  import org.hamcrest.MatcherAssert;
61  import org.junit.jupiter.api.Assertions;
62  import org.junit.jupiter.api.BeforeEach;
63  import org.junit.jupiter.api.Test;
64  import org.mockito.Mockito;
65  
66  public class TestBasicHttpCache {
67  
68      private HttpHost host;
69      private Instant now;
70      private Instant tenSecondsAgo;
71      private SimpleHttpCacheStorage backing;
72      private BasicHttpCache impl;
73  
74      @BeforeEach
75      public void setUp() throws Exception {
76          host = new HttpHost("foo.example.com");
77          now = Instant.now();
78          tenSecondsAgo = now.minusSeconds(10);
79          backing = Mockito.spy(new SimpleHttpCacheStorage());
80          impl = new BasicHttpCache(new HeapResourceFactory(), backing);
81      }
82  
83      @Test
84      public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
85          final HttpHost host = new HttpHost("foo.example.com");
86          final HttpRequest request = new HttpGet("http://foo.example.com/bar");
87          final CacheMatch result = impl.match(host, request);
88          assertNull(result);
89      }
90  
91      @Test
92      public void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception {
93          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
94          assertFalse(entry.hasVariants());
95          final HttpHost host = new HttpHost("foo.example.com");
96          final HttpRequest request = new HttpGet("http://foo.example.com/bar");
97  
98          final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
99  
100         backing.map.put(key,entry);
101 
102         final CacheMatch result = impl.match(host, request);
103         assertNotNull(result);
104         assertNotNull(result.hit);
105         assertSame(entry, result.hit.entry);
106     }
107 
108     @Test
109     public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
110         final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
111         origRequest.setHeader("Accept-Encoding","gzip");
112 
113         final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
114         final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
115         origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
116         origResponse.setHeader("Cache-Control", "max-age=3600, public");
117         origResponse.setHeader("ETag", "\"etag\"");
118         origResponse.setHeader("Vary", "Accept-Encoding");
119         origResponse.setHeader("Content-Encoding","gzip");
120 
121         impl.store(host, origRequest, origResponse, buf, now, now);
122 
123         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
124         final CacheMatch result = impl.match(host, request);
125         assertNotNull(result);
126         assertNull(result.hit);
127     }
128 
129     @Test
130     public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
131         final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
132         origRequest.setHeader("Accept-Encoding","gzip");
133 
134         final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
135         final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
136         origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
137         origResponse.setHeader("Cache-Control", "max-age=3600, public");
138         origResponse.setHeader("ETag", "\"etag\"");
139         origResponse.setHeader("Vary", "Accept-Encoding");
140         origResponse.setHeader("Content-Encoding","gzip");
141 
142         impl.store(host, origRequest, origResponse, buf, now, now);
143 
144         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
145         request.setHeader("Accept-Encoding","gzip");
146         final CacheMatch result = impl.match(host, request);
147         assertNotNull(result);
148         assertNotNull(result.hit);
149     }
150 
151     @Test
152     public void testGetCacheEntryReturnsVariantWithMostRecentDateHeader() throws Exception {
153         final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
154         origRequest.setHeader("Accept-Encoding", "gzip");
155 
156         final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
157 
158         // Create two response variants with different Date headers
159         final HttpResponse origResponse1 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
160         origResponse1.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now.minusSeconds(3600)));
161         origResponse1.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
162         origResponse1.setHeader(HttpHeaders.ETAG, "\"etag1\"");
163         origResponse1.setHeader(HttpHeaders.VARY, "Accept-Encoding");
164 
165         final HttpResponse origResponse2 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
166         origResponse2.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now));
167         origResponse2.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
168         origResponse2.setHeader(HttpHeaders.ETAG, "\"etag2\"");
169         origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
170 
171         // Store the two variants in cache
172         impl.store(host, origRequest, origResponse1, buf, now, now);
173         impl.store(host, origRequest, origResponse2, buf, now, now);
174 
175         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
176         request.setHeader("Accept-Encoding", "gzip");
177         final CacheMatch result = impl.match(host, request);
178         assertNotNull(result);
179         assertNotNull(result.hit);
180         final HttpCacheEntry entry = result.hit.entry;
181         assertNotNull(entry);
182 
183         // Retrieve the ETag header value from the original response and assert that
184         // the returned cache entry has the same ETag value
185         final String expectedEtag = origResponse2.getFirstHeader(HttpHeaders.ETAG).getValue();
186         final String actualEtag = entry.getFirstHeader(HttpHeaders.ETAG).getValue();
187 
188         assertEquals(expectedEtag, actualEtag);
189     }
190 
191     @Test
192     public void testGetVariantsRootNoVariants() throws Exception {
193         final HttpCacheEntry root = HttpTestUtils.makeCacheEntry();
194         final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
195 
196         assertNotNull(variants);
197         assertEquals(0, variants.size());
198     }
199 
200     @Test
201     public void testGetVariantsRootNonExistentVariants() throws Exception {
202         final Set<String> varinats = new HashSet<>();
203         varinats.add("variant1");
204         varinats.add("variant2");
205         final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(varinats);
206         final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
207 
208         assertNotNull(variants);
209         assertEquals(0, variants.size());
210     }
211 
212     @Test
213     public void testGetVariantCacheEntriesReturnsAllVariants() throws Exception {
214         final HttpHost host = new HttpHost("foo.example.com");
215         final URI uri = new URI("http://foo.example.com/bar");
216         final HttpRequest req1 = new HttpGet(uri);
217         req1.setHeader("Accept-Encoding", "gzip");
218 
219         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(uri);
220 
221         final HttpResponse resp1 = HttpTestUtils.make200Response();
222         resp1.setHeader("Date", DateUtils.formatStandardDate(now));
223         resp1.setHeader("Cache-Control", "max-age=3600, public");
224         resp1.setHeader("ETag", "\"etag1\"");
225         resp1.setHeader("Vary", "Accept-Encoding");
226         resp1.setHeader("Content-Encoding","gzip");
227 
228         final HttpRequest req2 = new HttpGet(uri);
229         req2.setHeader("Accept-Encoding", "identity");
230 
231         final HttpResponse resp2 = HttpTestUtils.make200Response();
232         resp2.setHeader("Date", DateUtils.formatStandardDate(now));
233         resp2.setHeader("Cache-Control", "max-age=3600, public");
234         resp2.setHeader("ETag", "\"etag2\"");
235         resp2.setHeader("Vary", "Accept-Encoding");
236         resp2.setHeader("Content-Encoding","gzip");
237 
238         final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
239         final CacheHit hit2 = impl.store(host, req2, resp2, null, now, now);
240 
241         final Set<String> variants = new HashSet<>();
242         variants.add("{accept-encoding=gzip}");
243         variants.add("{accept-encoding=identity}");
244 
245         final Map<String, HttpCacheEntry> variantMap = impl.getVariants(new CacheHit(hit1.rootKey,
246                         HttpTestUtils.makeCacheEntry(variants))).stream()
247                 .collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry));
248 
249         assertNotNull(variantMap);
250         assertEquals(2, variantMap.size());
251         MatcherAssert.assertThat(variantMap.get("{accept-encoding=gzip}" + rootKey),
252                 HttpCacheEntryMatcher.equivalent(hit1.entry));
253         MatcherAssert.assertThat(variantMap.get("{accept-encoding=identity}" + rootKey),
254                 HttpCacheEntryMatcher.equivalent(hit2.entry));
255     }
256 
257     @Test
258     public void testUpdateCacheEntry() throws Exception {
259         final HttpHost host = new HttpHost("foo.example.com");
260         final URI uri = new URI("http://foo.example.com/bar");
261         final HttpRequest req1 = new HttpGet(uri);
262 
263         final HttpResponse resp1 = HttpTestUtils.make200Response();
264         resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
265         resp1.setHeader("Cache-Control", "max-age=3600, public");
266         resp1.setHeader("ETag", "\"etag1\"");
267         resp1.setHeader("Content-Encoding","gzip");
268 
269         final HttpRequest revalidate = new HttpGet(uri);
270         revalidate.setHeader("If-None-Match","\"etag1\"");
271 
272         final HttpResponse resp2 = HttpTestUtils.make304Response();
273         resp2.setHeader("Date", DateUtils.formatStandardDate(now));
274         resp2.setHeader("Cache-Control", "max-age=3600, public");
275 
276         final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
277         Assertions.assertNotNull(hit1);
278         Assertions.assertEquals(1, backing.map.size());
279         Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
280 
281         final CacheHit updated = impl.update(hit1, host, req1, resp2, now, now);
282         Assertions.assertNotNull(updated);
283         Assertions.assertEquals(1, backing.map.size());
284         Assertions.assertSame(updated.entry, backing.map.get(hit1.getEntryKey()));
285 
286         MatcherAssert.assertThat(
287                 updated.entry.getHeaders(),
288                 HeadersMatcher.same(
289                         new BasicHeader("Server", "MockOrigin/1.0"),
290                         new BasicHeader("ETag", "\"etag1\""),
291                         new BasicHeader("Content-Encoding","gzip"),
292                         new BasicHeader("Date", DateUtils.formatStandardDate(now)),
293                         new BasicHeader("Cache-Control", "max-age=3600, public")
294                 ));
295     }
296 
297     @Test
298     public void testUpdateVariantCacheEntry() throws Exception {
299         final HttpHost host = new HttpHost("foo.example.com");
300         final URI uri = new URI("http://foo.example.com/bar");
301         final HttpRequest req1 = new HttpGet(uri);
302         req1.setHeader("User-Agent", "agent1");
303 
304         final HttpResponse resp1 = HttpTestUtils.make200Response();
305         resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
306         resp1.setHeader("Cache-Control", "max-age=3600, public");
307         resp1.setHeader("ETag", "\"etag1\"");
308         resp1.setHeader("Content-Encoding","gzip");
309         resp1.setHeader("Vary", "User-Agent");
310 
311         final HttpRequest revalidate = new HttpGet(uri);
312         revalidate.setHeader("If-None-Match","\"etag1\"");
313 
314         final HttpResponse resp2 = HttpTestUtils.make304Response();
315         resp2.setHeader("Date", DateUtils.formatStandardDate(now));
316         resp2.setHeader("Cache-Control", "max-age=3600, public");
317 
318         final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
319         Assertions.assertNotNull(hit1);
320         Assertions.assertEquals(2, backing.map.size());
321         Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
322 
323         final CacheHit updated = impl.update(hit1, host, req1, resp2, now, now);
324         Assertions.assertNotNull(updated);
325         Assertions.assertEquals(2, backing.map.size());
326         Assertions.assertSame(updated.entry, backing.map.get(hit1.getEntryKey()));
327 
328         MatcherAssert.assertThat(
329                 updated.entry.getHeaders(),
330                 HeadersMatcher.same(
331                         new BasicHeader("Server", "MockOrigin/1.0"),
332                         new BasicHeader("ETag", "\"etag1\""),
333                         new BasicHeader("Content-Encoding","gzip"),
334                         new BasicHeader("Vary","User-Agent"),
335                         new BasicHeader("Date", DateUtils.formatStandardDate(now)),
336                         new BasicHeader("Cache-Control", "max-age=3600, public")
337                 ));
338     }
339 
340     @Test
341     public void testUpdateCacheEntryTurnsVariant() throws Exception {
342         final HttpHost host = new HttpHost("foo.example.com");
343         final URI uri = new URI("http://foo.example.com/bar");
344         final HttpRequest req1 = new HttpGet(uri);
345         req1.setHeader("User-Agent", "agent1");
346 
347         final HttpResponse resp1 = HttpTestUtils.make200Response();
348         resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
349         resp1.setHeader("Cache-Control", "max-age=3600, public");
350         resp1.setHeader("ETag", "\"etag1\"");
351         resp1.setHeader("Content-Encoding","gzip");
352 
353         final HttpRequest revalidate = new HttpGet(uri);
354         revalidate.setHeader("If-None-Match","\"etag1\"");
355 
356         final HttpResponse resp2 = HttpTestUtils.make304Response();
357         resp2.setHeader("Date", DateUtils.formatStandardDate(now));
358         resp2.setHeader("Cache-Control", "max-age=3600, public");
359         resp2.setHeader("Vary", "User-Agent");
360 
361         final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
362         Assertions.assertNotNull(hit1);
363         Assertions.assertEquals(1, backing.map.size());
364         Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
365 
366         final CacheHit updated = impl.update(hit1, host, req1, resp2, now, now);
367         Assertions.assertNotNull(updated);
368         Assertions.assertEquals(2, backing.map.size());
369 
370         MatcherAssert.assertThat(
371                 updated.entry.getHeaders(),
372                 HeadersMatcher.same(
373                         new BasicHeader("Server", "MockOrigin/1.0"),
374                         new BasicHeader("ETag", "\"etag1\""),
375                         new BasicHeader("Content-Encoding","gzip"),
376                         new BasicHeader("Date", DateUtils.formatStandardDate(now)),
377                         new BasicHeader("Cache-Control", "max-age=3600, public"),
378                         new BasicHeader("Vary","User-Agent")));
379     }
380 
381     @Test
382     public void testStoreFromNegotiatedVariant() throws Exception {
383         final HttpHost host = new HttpHost("foo.example.com");
384         final URI uri = new URI("http://foo.example.com/bar");
385         final HttpRequest req1 = new HttpGet(uri);
386         req1.setHeader("User-Agent", "agent1");
387 
388         final HttpResponse resp1 = HttpTestUtils.make200Response();
389         resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
390         resp1.setHeader("Cache-Control", "max-age=3600, public");
391         resp1.setHeader("ETag", "\"etag1\"");
392         resp1.setHeader("Content-Encoding","gzip");
393         resp1.setHeader("Vary", "User-Agent");
394 
395         final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
396         Assertions.assertNotNull(hit1);
397         Assertions.assertEquals(2, backing.map.size());
398         Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
399 
400         final HttpRequest req2 = new HttpGet(uri);
401         req2.setHeader("User-Agent", "agent2");
402 
403         final HttpResponse resp2 = HttpTestUtils.make304Response();
404         resp2.setHeader("Date", DateUtils.formatStandardDate(now));
405         resp2.setHeader("Cache-Control", "max-age=3600, public");
406 
407         final CacheHit hit2 = impl.storeFromNegotiated(hit1, host, req2, resp2, now, now);
408         Assertions.assertNotNull(hit2);
409         Assertions.assertEquals(3, backing.map.size());
410 
411         MatcherAssert.assertThat(
412                 hit2.entry.getHeaders(),
413                 HeadersMatcher.same(
414                         new BasicHeader("Server", "MockOrigin/1.0"),
415                         new BasicHeader("ETag", "\"etag1\""),
416                         new BasicHeader("Content-Encoding","gzip"),
417                         new BasicHeader("Vary","User-Agent"),
418                         new BasicHeader("Date", DateUtils.formatStandardDate(now)),
419                         new BasicHeader("Cache-Control", "max-age=3600, public")));
420     }
421 
422     @Test
423     public void testInvalidatesUnsafeRequests() throws Exception {
424         final HttpRequest request = new BasicHttpRequest("POST","/path");
425         final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
426 
427         final HttpResponse response = HttpTestUtils.make200Response();
428 
429         backing.putEntry(key, HttpTestUtils.makeCacheEntry());
430 
431         impl.evictInvalidatedEntries(host, request, response);
432 
433         verify(backing).getEntry(key);
434         verify(backing).removeEntry(key);
435 
436         Assertions.assertNull(backing.getEntry(key));
437     }
438 
439     @Test
440     public void testDoesNotInvalidateSafeRequests() throws Exception {
441         final HttpRequest request1 = new BasicHttpRequest("GET","/");
442         final HttpResponse response1 = HttpTestUtils.make200Response();
443 
444         impl.evictInvalidatedEntries(host, request1, response1);
445 
446         verifyNoMoreInteractions(backing);
447 
448         final HttpRequest request2 = new BasicHttpRequest("HEAD","/");
449         final HttpResponse response2 = HttpTestUtils.make200Response();
450         impl.evictInvalidatedEntries(host, request2, response2);
451 
452         verifyNoMoreInteractions(backing);
453     }
454 
455     @Test
456     public void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
457         final HttpRequest request = new BasicHttpRequest("POST","/path");
458         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
459         final Set<String> variants = new HashSet<>();
460         variants.add("{var1}");
461         variants.add("{var2}");
462         final String variantKey1 = "{var1}" + rootKey;
463         final String variantKey2 = "{var2}" + rootKey;
464 
465         final HttpResponse response = HttpTestUtils.make200Response();
466 
467         backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variants));
468         backing.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
469         backing.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());
470 
471         impl.evictInvalidatedEntries(host, request, response);
472 
473         verify(backing).getEntry(rootKey);
474         verify(backing).removeEntry(rootKey);
475         verify(backing).removeEntry(variantKey1);
476         verify(backing).removeEntry(variantKey2);
477 
478         Assertions.assertNull(backing.getEntry(rootKey));
479         Assertions.assertNull(backing.getEntry(variantKey1));
480         Assertions.assertNull(backing.getEntry(variantKey2));
481     }
482 
483     @Test
484     public void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
485         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
486         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
487         final URI contentUri = new URIBuilder()
488                 .setHttpHost(host)
489                 .setPath("/bar")
490                 .build();
491         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
492 
493         final HttpResponse response = HttpTestUtils.make200Response();
494         response.setHeader("ETag","\"new-etag\"");
495         response.setHeader("Date", DateUtils.formatStandardDate(now));
496         response.setHeader("Content-Location", contentUri.toASCIIString());
497 
498         backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
499         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
500                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
501                 new BasicHeader("ETag", "\"old-etag\"")
502         ));
503 
504         impl.evictInvalidatedEntries(host, request, response);
505 
506         verify(backing).getEntry(rootKey);
507         verify(backing).removeEntry(rootKey);
508         verify(backing).getEntry(contentKey);
509         verify(backing).removeEntry(contentKey);
510     }
511 
512     @Test
513     public void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
514         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
515         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
516         final URI contentUri = new URIBuilder()
517                 .setHttpHost(host)
518                 .setPath("/bar")
519                 .build();
520         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
521 
522         final HttpResponse response = HttpTestUtils.make200Response();
523         response.setHeader("ETag","\"new-etag\"");
524         response.setHeader("Date", DateUtils.formatStandardDate(now));
525         response.setHeader("Location", contentUri.toASCIIString());
526 
527         backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
528         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
529                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
530                 new BasicHeader("ETag", "\"old-etag\"")
531         ));
532 
533         impl.evictInvalidatedEntries(host, request, response);
534 
535         verify(backing).getEntry(rootKey);
536         verify(backing).removeEntry(rootKey);
537         verify(backing).getEntry(contentKey);
538         verify(backing).removeEntry(contentKey);
539     }
540 
541     @Test
542     public void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
543         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
544         final URI contentUri = new URIBuilder()
545                 .setHttpHost(host)
546                 .setPath("/bar")
547                 .build();
548         final HttpResponse response = HttpTestUtils.make500Response();
549         response.setHeader("ETag","\"new-etag\"");
550         response.setHeader("Date", DateUtils.formatStandardDate(now));
551         response.setHeader("Content-Location", contentUri.toASCIIString());
552 
553         impl.evictInvalidatedEntries(host, request, response);
554 
555         verifyNoMoreInteractions(backing);
556     }
557 
558     @Test
559     public void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
560         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
561         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
562         final URI contentUri = new URIBuilder()
563                 .setHttpHost(host)
564                 .setPath("/bar")
565                 .build();
566         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
567 
568         final HttpResponse response = HttpTestUtils.make200Response();
569         response.setHeader("ETag","\"new-etag\"");
570         response.setHeader("Date", DateUtils.formatStandardDate(now));
571 
572         response.setHeader("Content-Location", contentUri.toASCIIString());
573 
574         backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
575 
576         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
577                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
578                 new BasicHeader("ETag", "\"old-etag\"")));
579 
580         impl.evictInvalidatedEntries(host, request, response);
581 
582         verify(backing).getEntry(rootKey);
583         verify(backing).removeEntry(rootKey);
584         verify(backing).getEntry(contentKey);
585         verify(backing).removeEntry(contentKey);
586         Assertions.assertNull(backing.getEntry(rootKey));
587         Assertions.assertNull(backing.getEntry(contentKey));
588     }
589 
590     @Test
591     public void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
592         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
593         final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
594         final URI contentUri = new URIBuilder()
595                 .setHttpHost(host)
596                 .setPath("/bar")
597                 .build();
598         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
599 
600         final HttpResponse response = HttpTestUtils.make200Response();
601         response.setHeader("ETag","\"new-etag\"");
602         response.setHeader("Date", DateUtils.formatStandardDate(now));
603 
604         response.setHeader("Content-Location", "/bar");
605 
606         backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
607 
608         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
609                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
610                 new BasicHeader("ETag", "\"old-etag\"")));
611 
612         impl.evictInvalidatedEntries(host, request, response);
613 
614         verify(backing).getEntry(rootKey);
615         verify(backing).removeEntry(rootKey);
616         verify(backing).getEntry(contentKey);
617         verify(backing).removeEntry(contentKey);
618         Assertions.assertNull(backing.getEntry(rootKey));
619         Assertions.assertNull(backing.getEntry(contentKey));
620     }
621 
622     @Test
623     public void testDoesNotInvalidateUriSpecifiedByContentLocationOtherOrigin() throws Exception {
624         final HttpRequest request = new BasicHttpRequest("PUT", "/");
625         final URI contentUri = new URIBuilder()
626                 .setHost("bar.example.com")
627                 .setPath("/")
628                 .build();
629         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
630 
631         final HttpResponse response = HttpTestUtils.make200Response();
632         response.setHeader("ETag","\"new-etag\"");
633         response.setHeader("Date", DateUtils.formatStandardDate(now));
634         response.setHeader("Content-Location", contentUri.toASCIIString());
635 
636         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry());
637 
638         impl.evictInvalidatedEntries(host, request, response);
639 
640         verify(backing, Mockito.never()).getEntry(contentKey);
641         verify(backing, Mockito.never()).removeEntry(contentKey);
642     }
643 
644     @Test
645     public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEtagsMatch() throws Exception {
646         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
647         final URI contentUri = new URIBuilder()
648                 .setHttpHost(host)
649                 .setPath("/bar")
650                 .build();
651         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
652 
653         final HttpResponse response = HttpTestUtils.make200Response();
654         response.setHeader("ETag","\"same-etag\"");
655         response.setHeader("Date", DateUtils.formatStandardDate(now));
656         response.setHeader("Content-Location", contentUri.toASCIIString());
657 
658         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
659                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
660                 new BasicHeader("ETag", "\"same-etag\"")));
661 
662         impl.evictInvalidatedEntries(host, request, response);
663 
664         verify(backing).getEntry(contentKey);
665         verify(backing, Mockito.never()).removeEntry(contentKey);
666     }
667 
668     @Test
669     public void testDoesNotInvalidateUriSpecifiedByContentLocationIfOlder() throws Exception {
670         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
671         final URI contentUri = new URIBuilder()
672                 .setHttpHost(host)
673                 .setPath("/bar")
674                 .build();
675         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
676 
677         final HttpResponse response = HttpTestUtils.make200Response();
678         response.setHeader("ETag","\"new-etag\"");
679         response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
680         response.setHeader("Content-Location", contentUri.toASCIIString());
681 
682         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
683                 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
684                 new BasicHeader("ETag", "\"old-etag\"")));
685 
686         impl.evictInvalidatedEntries(host, request, response);
687 
688         verify(backing).getEntry(contentKey);
689         verify(backing, Mockito.never()).removeEntry(contentKey);
690     }
691 
692     @Test
693     public void testDoesNotInvalidateUriSpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
694         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
695         final URI contentUri = new URIBuilder()
696                 .setHttpHost(host)
697                 .setPath("/bar")
698                 .build();
699         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
700 
701         final HttpResponse response = HttpTestUtils.make200Response();
702         response.removeHeaders("ETag");
703         response.setHeader("Date", DateUtils.formatStandardDate(now));
704         response.setHeader("Content-Location", contentUri.toASCIIString());
705 
706         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
707                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
708                 new BasicHeader("ETag", "\"old-etag\"")));
709 
710         impl.evictInvalidatedEntries(host, request, response);
711 
712         verify(backing).getEntry(contentKey);
713         verify(backing, Mockito.never()).removeEntry(contentKey);
714     }
715 
716     @Test
717     public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
718         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
719         final URI contentUri = new URIBuilder()
720                 .setHttpHost(host)
721                 .setPath("/bar")
722                 .build();
723         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
724 
725         final HttpResponse response = HttpTestUtils.make200Response();
726         response.setHeader("ETag", "\"some-etag\"");
727         response.setHeader("Date", DateUtils.formatStandardDate(now));
728         response.setHeader("Content-Location", contentUri.toASCIIString());
729 
730         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
731                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
732 
733         impl.evictInvalidatedEntries(host, request, response);
734 
735         verify(backing).getEntry(contentKey);
736         verify(backing, Mockito.never()).removeEntry(contentKey);
737     }
738 
739     @Test
740     public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
741         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
742         final URI contentUri = new URIBuilder()
743                 .setHttpHost(host)
744                 .setPath("/bar")
745                 .build();
746         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
747 
748         final HttpResponse response = HttpTestUtils.make200Response();
749         response.setHeader("ETag", "\"new-etag\"");
750         response.removeHeaders("Date");
751         response.setHeader("Content-Location", contentUri.toASCIIString());
752 
753         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
754                 new BasicHeader("ETag", "\"old-etag\""),
755                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
756 
757         impl.evictInvalidatedEntries(host, request, response);
758 
759         verify(backing).getEntry(contentKey);
760         verify(backing).removeEntry(contentKey);
761     }
762 
763     @Test
764     public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
765         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
766         final URI contentUri = new URIBuilder()
767                 .setHttpHost(host)
768                 .setPath("/bar")
769                 .build();
770         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
771 
772         final HttpResponse response = HttpTestUtils.make200Response();
773         response.setHeader("ETag","\"new-etag\"");
774         response.setHeader("Date", DateUtils.formatStandardDate(now));
775         response.setHeader("Content-Location", contentUri.toASCIIString());
776 
777         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
778                 new BasicHeader("ETag", "\"old-etag\"")));
779 
780         impl.evictInvalidatedEntries(host, request, response);
781 
782         verify(backing).getEntry(contentKey);
783         verify(backing).removeEntry(contentKey);
784     }
785 
786     @Test
787     public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
788         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
789         final URI contentUri = new URIBuilder()
790                 .setHttpHost(host)
791                 .setPath("/bar")
792                 .build();
793         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
794 
795         final HttpResponse response = HttpTestUtils.make200Response();
796         response.setHeader("ETag","\"new-etag\"");
797         response.setHeader("Date", "huh?");
798         response.setHeader("Content-Location", contentUri.toASCIIString());
799 
800         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
801                 new BasicHeader("ETag", "\"old-etag\""),
802                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
803 
804         impl.evictInvalidatedEntries(host, request, response);
805 
806         verify(backing).getEntry(contentKey);
807         verify(backing).removeEntry(contentKey);
808     }
809 
810     @Test
811     public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
812         final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
813         final URI contentUri = new URIBuilder()
814                 .setHttpHost(host)
815                 .setPath("/bar")
816                 .build();
817         final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
818 
819         final HttpResponse response = HttpTestUtils.make200Response();
820         response.setHeader("ETag","\"new-etag\"");
821         response.setHeader("Date", DateUtils.formatStandardDate(now));
822         response.setHeader("Content-Location", contentUri.toASCIIString());
823 
824         backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
825                 new BasicHeader("ETag", "\"old-etag\""),
826                 new BasicHeader("Date", "huh?")));
827 
828         impl.evictInvalidatedEntries(host, request, response);
829 
830         verify(backing).getEntry(contentKey);
831         verify(backing).removeEntry(contentKey);
832     }
833 
834 }