1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 }