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.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 }