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  import java.time.Instant;
30  import java.util.HashMap;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import org.apache.hc.client5.http.cache.HeaderConstants;
35  import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
36  import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
37  import org.apache.hc.client5.http.cache.HttpCacheEntry;
38  import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
39  import org.apache.hc.client5.http.cache.ResourceFactory;
40  import org.apache.hc.client5.http.cache.ResourceIOException;
41  import org.apache.hc.client5.http.impl.Operations;
42  import org.apache.hc.core5.concurrent.Cancellable;
43  import org.apache.hc.core5.concurrent.ComplexCancellable;
44  import org.apache.hc.core5.concurrent.FutureCallback;
45  import org.apache.hc.core5.http.Header;
46  import org.apache.hc.core5.http.HttpHost;
47  import org.apache.hc.core5.http.HttpRequest;
48  import org.apache.hc.core5.http.HttpResponse;
49  import org.apache.hc.core5.http.Method;
50  import org.apache.hc.core5.http.message.RequestLine;
51  import org.apache.hc.core5.http.message.StatusLine;
52  import org.apache.hc.core5.util.ByteArrayBuffer;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  class BasicHttpAsyncCache implements HttpAsyncCache {
57  
58      private static final Logger LOG = LoggerFactory.getLogger(BasicHttpAsyncCache.class);
59  
60      private final CacheUpdateHandler cacheUpdateHandler;
61      private final CacheKeyGenerator cacheKeyGenerator;
62      private final HttpAsyncCacheInvalidator cacheInvalidator;
63      private final HttpAsyncCacheStorage storage;
64  
65      public BasicHttpAsyncCache(
66              final ResourceFactory resourceFactory,
67              final HttpAsyncCacheStorage storage,
68              final CacheKeyGenerator cacheKeyGenerator,
69              final HttpAsyncCacheInvalidator cacheInvalidator) {
70          this.cacheUpdateHandler = new CacheUpdateHandler(resourceFactory);
71          this.cacheKeyGenerator = cacheKeyGenerator;
72          this.storage = storage;
73          this.cacheInvalidator = cacheInvalidator;
74      }
75  
76      public BasicHttpAsyncCache(
77              final ResourceFactory resourceFactory,
78              final HttpAsyncCacheStorage storage,
79              final CacheKeyGenerator cacheKeyGenerator) {
80          this(resourceFactory, storage, cacheKeyGenerator, DefaultAsyncCacheInvalidator.INSTANCE);
81      }
82  
83      public BasicHttpAsyncCache(final ResourceFactory resourceFactory, final HttpAsyncCacheStorage storage) {
84          this( resourceFactory, storage, CacheKeyGenerator.INSTANCE);
85      }
86  
87      @Override
88      public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
89          if (cacheEntry == null) {
90              return cacheKeyGenerator.generateKey(host, request);
91          } else {
92              return cacheKeyGenerator.generateKey(host, request, cacheEntry);
93          }
94      }
95  
96      @Override
97      public Cancellable flushCacheEntriesFor(
98              final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
99          if (LOG.isDebugEnabled()) {
100             LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
101         }
102         if (!Method.isSafe(request.getMethod())) {
103             final String cacheKey = cacheKeyGenerator.generateKey(host, request);
104             return storage.removeEntry(cacheKey, new FutureCallback<Boolean>() {
105 
106                 @Override
107                 public void completed(final Boolean result) {
108                     callback.completed(result);
109                 }
110 
111                 @Override
112                 public void failed(final Exception ex) {
113                     if (ex instanceof ResourceIOException) {
114                         if (LOG.isWarnEnabled()) {
115                             LOG.warn("I/O error removing cache entry with key {}", cacheKey);
116                         }
117                         callback.completed(Boolean.TRUE);
118                     } else {
119                         callback.failed(ex);
120                     }
121                 }
122 
123                 @Override
124                 public void cancelled() {
125                     callback.cancelled();
126                 }
127 
128             });
129         }
130         callback.completed(Boolean.TRUE);
131         return Operations.nonCancellable();
132     }
133 
134     @Override
135     public Cancellable flushCacheEntriesInvalidatedByRequest(
136             final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
137         if (LOG.isDebugEnabled()) {
138             LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
139         }
140         return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
141     }
142 
143     @Override
144     public Cancellable flushCacheEntriesInvalidatedByExchange(
145             final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
146         if (LOG.isDebugEnabled()) {
147             LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
148         }
149         if (!Method.isSafe(request.getMethod())) {
150             return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
151         }
152         callback.completed(Boolean.TRUE);
153         return Operations.nonCancellable();
154     }
155 
156     Cancellable storeInCache(
157             final String cacheKey,
158             final HttpHost host,
159             final HttpRequest request,
160             final HttpCacheEntry entry,
161             final FutureCallback<Boolean> callback) {
162         if (entry.hasVariants()) {
163             return storeVariantEntry(cacheKey, host, request, entry, callback);
164         } else {
165             return storeEntry(cacheKey, entry, callback);
166         }
167     }
168 
169     Cancellable storeEntry(
170             final String cacheKey,
171             final HttpCacheEntry entry,
172             final FutureCallback<Boolean> callback) {
173         return storage.putEntry(cacheKey, entry, new FutureCallback<Boolean>() {
174 
175             @Override
176             public void completed(final Boolean result) {
177                 callback.completed(result);
178             }
179 
180             @Override
181             public void failed(final Exception ex) {
182                 if (ex instanceof ResourceIOException) {
183                     if (LOG.isWarnEnabled()) {
184                         LOG.warn("I/O error storing cache entry with key {}", cacheKey);
185                     }
186                     callback.completed(Boolean.TRUE);
187                 } else {
188                     callback.failed(ex);
189                 }
190             }
191 
192             @Override
193             public void cancelled() {
194                 callback.cancelled();
195             }
196 
197         });
198     }
199 
200     Cancellable storeVariantEntry(
201             final String cacheKey,
202             final HttpHost host,
203             final HttpRequest req,
204             final HttpCacheEntry entry,
205             final FutureCallback<Boolean> callback) {
206         final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
207         final String variantCacheKey = cacheKeyGenerator.generateKey(host, req, entry);
208         return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
209 
210             @Override
211             public void completed(final Boolean result) {
212                 storage.updateEntry(cacheKey,
213                         existing -> cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey),
214                         new FutureCallback<Boolean>() {
215 
216                             @Override
217                             public void completed(final Boolean result) {
218                                 callback.completed(result);
219                             }
220 
221                             @Override
222                             public void failed(final Exception ex) {
223                                 if (ex instanceof HttpCacheUpdateException) {
224                                     if (LOG.isWarnEnabled()) {
225                                         LOG.warn("Cannot update cache entry with key {}", cacheKey);
226                                     }
227                                 } else if (ex instanceof ResourceIOException) {
228                                     if (LOG.isWarnEnabled()) {
229                                         LOG.warn("I/O error updating cache entry with key {}", cacheKey);
230                                     }
231                                 } else {
232                                     callback.failed(ex);
233                                 }
234                             }
235 
236                             @Override
237                             public void cancelled() {
238                                 callback.cancelled();
239                             }
240 
241                         });
242             }
243 
244             @Override
245             public void failed(final Exception ex) {
246                 if (ex instanceof ResourceIOException) {
247                     if (LOG.isWarnEnabled()) {
248                         LOG.warn("I/O error updating cache entry with key {}", variantCacheKey);
249                     }
250                     callback.completed(Boolean.TRUE);
251                 } else {
252                     callback.failed(ex);
253                 }
254             }
255 
256             @Override
257             public void cancelled() {
258                 callback.cancelled();
259             }
260 
261         });
262     }
263 
264     @Override
265     public Cancellable reuseVariantEntryFor(
266             final HttpHost host, final HttpRequest request, final Variant variant, final FutureCallback<Boolean> callback) {
267         if (LOG.isDebugEnabled()) {
268             LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), variant);
269         }
270         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
271         final HttpCacheEntry entry = variant.getEntry();
272         final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
273         final String variantCacheKey = variant.getCacheKey();
274         return storage.updateEntry(cacheKey,
275                 existing -> cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey),
276                 new FutureCallback<Boolean>() {
277 
278                     @Override
279                     public void completed(final Boolean result) {
280                         callback.completed(result);
281                     }
282 
283                     @Override
284                     public void failed(final Exception ex) {
285                         if (ex instanceof HttpCacheUpdateException) {
286                             if (LOG.isWarnEnabled()) {
287                                 LOG.warn("Cannot update cache entry with key {}", cacheKey);
288                             }
289                         } else if (ex instanceof ResourceIOException) {
290                             if (LOG.isWarnEnabled()) {
291                                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
292                             }
293                         } else {
294                             callback.failed(ex);
295                         }
296                     }
297 
298                     @Override
299                     public void cancelled() {
300                         callback.cancelled();
301                     }
302 
303                 });
304     }
305 
306     @Override
307     public Cancellable updateCacheEntry(
308             final HttpHost host,
309             final HttpRequest request,
310             final HttpCacheEntry stale,
311             final HttpResponse originResponse,
312             final Instant requestSent,
313             final Instant responseReceived,
314             final FutureCallback<HttpCacheEntry> callback) {
315         if (LOG.isDebugEnabled()) {
316             LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
317         }
318         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
319         try {
320             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
321                     request.getRequestUri(),
322                     stale,
323                     requestSent,
324                     responseReceived,
325                     originResponse);
326             return storeInCache(cacheKey, host, request, updatedEntry, new FutureCallback<Boolean>() {
327 
328                 @Override
329                 public void completed(final Boolean result) {
330                     callback.completed(updatedEntry);
331                 }
332 
333                 @Override
334                 public void failed(final Exception ex) {
335                     callback.failed(ex);
336                 }
337 
338                 @Override
339                 public void cancelled() {
340                     callback.cancelled();
341                 }
342 
343             });
344         } catch (final ResourceIOException ex) {
345             if (LOG.isWarnEnabled()) {
346                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
347             }
348             callback.completed(stale);
349             return Operations.nonCancellable();
350         }
351     }
352 
353     @Override
354     public Cancellable updateVariantCacheEntry(
355             final HttpHost host,
356             final HttpRequest request,
357             final HttpResponse originResponse,
358             final Variant variant,
359             final Instant requestSent,
360             final Instant responseReceived,
361             final FutureCallback<HttpCacheEntry> callback) {
362         if (LOG.isDebugEnabled()) {
363             LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), variant);
364         }
365         final HttpCacheEntry entry = variant.getEntry();
366         final String cacheKey = variant.getCacheKey();
367         try {
368             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
369                     request.getRequestUri(),
370                     entry,
371                     requestSent,
372                     responseReceived,
373                     originResponse);
374             return storeEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() {
375 
376                 @Override
377                 public void completed(final Boolean result) {
378                     callback.completed(updatedEntry);
379                 }
380 
381                 @Override
382                 public void failed(final Exception ex) {
383                     callback.failed(ex);
384                 }
385 
386                 @Override
387                 public void cancelled() {
388                     callback.cancelled();
389                 }
390 
391             });
392         } catch (final ResourceIOException ex) {
393             if (LOG.isWarnEnabled()) {
394                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
395             }
396             callback.completed(entry);
397             return Operations.nonCancellable();
398         }
399     }
400 
401     @Override
402     public Cancellable createCacheEntry(
403             final HttpHost host,
404             final HttpRequest request,
405             final HttpResponse originResponse,
406             final ByteArrayBuffer content,
407             final Instant requestSent,
408             final Instant responseReceived,
409             final FutureCallback<HttpCacheEntry> callback) {
410         if (LOG.isDebugEnabled()) {
411             LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
412         }
413         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
414         try {
415             final HttpCacheEntry entry = cacheUpdateHandler.createCacheEntry(request, originResponse, content, requestSent, responseReceived);
416             return storeInCache(cacheKey, host, request, entry, new FutureCallback<Boolean>() {
417 
418                 @Override
419                 public void completed(final Boolean result) {
420                     callback.completed(entry);
421                 }
422 
423                 @Override
424                 public void failed(final Exception ex) {
425                     callback.failed(ex);
426                 }
427 
428                 @Override
429                 public void cancelled() {
430                     callback.cancelled();
431                 }
432 
433             });
434         } catch (final ResourceIOException ex) {
435             if (LOG.isWarnEnabled()) {
436                 LOG.warn("I/O error creating cache entry with key {}", cacheKey);
437             }
438             callback.completed(new HttpCacheEntry(
439                     requestSent,
440                     responseReceived,
441                     originResponse.getCode(),
442                     originResponse.getHeaders(),
443                     content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null));
444             return Operations.nonCancellable();
445         }
446     }
447 
448     @Override
449     public Cancellable getCacheEntry(final HttpHost host, final HttpRequest request, final FutureCallback<HttpCacheEntry> callback) {
450         if (LOG.isDebugEnabled()) {
451             LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
452         }
453         final ComplexCancellable complexCancellable = new ComplexCancellable();
454         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
455         complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
456 
457             @Override
458             public void completed(final HttpCacheEntry root) {
459                 if (root != null) {
460                     if (root.hasVariants()) {
461                         final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
462                         final String variantCacheKey = root.getVariantMap().get(variantKey);
463                         if (variantCacheKey != null) {
464                             complexCancellable.setDependency(storage.getEntry(
465                                     variantCacheKey,
466                                     new FutureCallback<HttpCacheEntry>() {
467 
468                                         @Override
469                                         public void completed(final HttpCacheEntry result) {
470                                             callback.completed(result);
471                                         }
472 
473                                         @Override
474                                         public void failed(final Exception ex) {
475                                             if (ex instanceof ResourceIOException) {
476                                                 if (LOG.isWarnEnabled()) {
477                                                     LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
478                                                 }
479                                                 callback.completed(null);
480                                             } else {
481                                                 callback.failed(ex);
482                                             }
483                                         }
484 
485                                         @Override
486                                         public void cancelled() {
487                                             callback.cancelled();
488                                         }
489 
490                                     }));
491                             return;
492                         }
493                     }
494                 }
495                 callback.completed(root);
496             }
497 
498             @Override
499             public void failed(final Exception ex) {
500                 if (ex instanceof ResourceIOException) {
501                     if (LOG.isWarnEnabled()) {
502                         LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
503                     }
504                     callback.completed(null);
505                 } else {
506                     callback.failed(ex);
507                 }
508             }
509 
510             @Override
511             public void cancelled() {
512                 callback.cancelled();
513             }
514 
515         }));
516         return complexCancellable;
517     }
518 
519     @Override
520     public Cancellable getVariantCacheEntriesWithEtags(
521             final HttpHost host, final HttpRequest request, final FutureCallback<Map<String, Variant>> callback) {
522         if (LOG.isDebugEnabled()) {
523             LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
524         }
525         final ComplexCancellable complexCancellable = new ComplexCancellable();
526         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
527         final Map<String, Variant> variants = new HashMap<>();
528         complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
529 
530             @Override
531             public void completed(final HttpCacheEntry rootEntry) {
532                 if (rootEntry != null && rootEntry.hasVariants()) {
533                     final Set<String> variantCacheKeys = rootEntry.getVariantMap().keySet();
534                     complexCancellable.setDependency(storage.getEntries(
535                             variantCacheKeys,
536                             new FutureCallback<Map<String, HttpCacheEntry>>() {
537 
538                                 @Override
539                                 public void completed(final Map<String, HttpCacheEntry> resultMap) {
540                                     for (final Map.Entry<String, HttpCacheEntry> resultMapEntry : resultMap.entrySet()) {
541                                         final String cacheKey = resultMapEntry.getKey();
542                                         final HttpCacheEntry cacheEntry = resultMapEntry.getValue();
543                                         final Header etagHeader = cacheEntry.getFirstHeader(HeaderConstants.ETAG);
544                                         if (etagHeader != null) {
545                                             variants.put(etagHeader.getValue(), new Variant(cacheKey, cacheEntry));
546                                         }
547                                     }
548                                     callback.completed(variants);
549                                 }
550 
551                                 @Override
552                                 public void failed(final Exception ex) {
553                                     if (ex instanceof ResourceIOException) {
554                                         if (LOG.isWarnEnabled()) {
555                                             LOG.warn("I/O error retrieving cache entry with keys {}", variantCacheKeys);
556                                         }
557                                         callback.completed(variants);
558                                     } else {
559                                         callback.failed(ex);
560                                     }
561                                 }
562 
563                                 @Override
564                                 public void cancelled() {
565                                     callback.cancelled();
566                                 }
567 
568                             }));
569                 } else {
570                     callback.completed(variants);
571                 }
572             }
573 
574             @Override
575             public void failed(final Exception ex) {
576                 if (ex instanceof ResourceIOException) {
577                     if (LOG.isWarnEnabled()) {
578                         LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
579                     }
580                     callback.completed(variants);
581                 } else {
582                     callback.failed(ex);
583                 }
584             }
585 
586             @Override
587             public void cancelled() {
588                 callback.cancelled();
589             }
590 
591         }));
592         return complexCancellable;
593     }
594 
595 }