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