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.cache;
28
29 import java.io.Serializable;
30 import java.time.Instant;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.atomic.AtomicReference;
40 import java.util.function.Function;
41 import java.util.stream.Collectors;
42
43 import org.apache.hc.client5.http.utils.DateUtils;
44 import org.apache.hc.client5.http.validator.ETag;
45 import org.apache.hc.core5.annotation.Contract;
46 import org.apache.hc.core5.annotation.Internal;
47 import org.apache.hc.core5.annotation.ThreadingBehavior;
48 import org.apache.hc.core5.http.Header;
49 import org.apache.hc.core5.http.HttpHeaders;
50 import org.apache.hc.core5.http.HttpStatus;
51 import org.apache.hc.core5.http.MessageHeaders;
52 import org.apache.hc.core5.http.Method;
53 import org.apache.hc.core5.http.ProtocolException;
54 import org.apache.hc.core5.http.message.HeaderGroup;
55 import org.apache.hc.core5.util.Args;
56
57
58
59
60
61
62
63
64
65
66 @Contract(threading = ThreadingBehavior.IMMUTABLE)
67 public class HttpCacheEntry implements MessageHeaders, Serializable {
68
69 private static final long serialVersionUID = -6300496422359477413L;
70
71 private final Instant requestDate;
72 private final Instant responseDate;
73 private final String method;
74 private final String requestURI;
75 private final HeaderGroup requestHeaders;
76 private final int status;
77 private final HeaderGroup responseHeaders;
78 private final Resource resource;
79 private final Set<String> variants;
80 private final AtomicReference<Instant> dateRef;
81 private final AtomicReference<Instant> expiresRef;
82 private final AtomicReference<Instant> lastModifiedRef;
83
84 private final AtomicReference<ETag> eTagRef;
85
86
87
88
89
90 @Internal
91 public HttpCacheEntry(
92 final Instant requestDate,
93 final Instant responseDate,
94 final String method,
95 final String requestURI,
96 final HeaderGroup requestHeaders,
97 final int status,
98 final HeaderGroup responseHeaders,
99 final Resource resource,
100 final Collection<String> variants) {
101 super();
102 this.requestDate = requestDate;
103 this.responseDate = responseDate;
104 this.method = method;
105 this.requestURI = requestURI;
106 this.requestHeaders = requestHeaders;
107 this.status = status;
108 this.responseHeaders = responseHeaders;
109 this.resource = resource;
110 this.variants = variants != null ? Collections.unmodifiableSet(new HashSet<>(variants)) : null;
111 this.dateRef = new AtomicReference<>();
112 this.expiresRef = new AtomicReference<>();
113 this.lastModifiedRef = new AtomicReference<>();
114 this.eTagRef = new AtomicReference<>();
115 }
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 @Deprecated
137 public HttpCacheEntry(
138 final Date requestDate,
139 final Date responseDate,
140 final int status,
141 final Header[] responseHeaders,
142 final Resource resource,
143 final Map<String, String> variantMap) {
144 this(DateUtils.toInstant(requestDate), DateUtils.toInstant(responseDate), status, responseHeaders, resource, variantMap);
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 @Deprecated
162 public HttpCacheEntry(
163 final Instant requestDate,
164 final Instant responseDate,
165 final int status,
166 final Header[] responseHeaders,
167 final Resource resource,
168 final Map<String, String> variantMap) {
169 super();
170 Args.notNull(requestDate, "Request date");
171 Args.notNull(responseDate, "Response date");
172 Args.check(status >= HttpStatus.SC_SUCCESS, "Status code");
173 Args.notNull(responseHeaders, "Response headers");
174 this.requestDate = requestDate;
175 this.responseDate = responseDate;
176 this.method = Method.GET.name();
177 this.requestURI = "/";
178 this.requestHeaders = new HeaderGroup();
179 this.status = status;
180 this.responseHeaders = new HeaderGroup();
181 this.responseHeaders.setHeaders(responseHeaders);
182 this.resource = resource;
183 this.variants = variantMap != null ? Collections.unmodifiableSet(new HashSet<>(variantMap.keySet())) : null;
184 this.dateRef = new AtomicReference<>();
185 this.expiresRef = new AtomicReference<>();
186 this.lastModifiedRef = new AtomicReference<>();
187 this.eTagRef = new AtomicReference<>();
188 }
189
190
191
192
193
194
195
196
197
198
199
200 @Deprecated
201 public HttpCacheEntry(final Date requestDate, final Date responseDate, final int status,
202 final Header[] responseHeaders, final Resource resource) {
203 this(requestDate, responseDate, status, responseHeaders, resource, new HashMap<>());
204 }
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 @Deprecated
224 public HttpCacheEntry(final Instant requestDate, final Instant responseDate, final int status,
225 final Header[] responseHeaders, final Resource resource) {
226 this(requestDate, responseDate, status, responseHeaders, resource, new HashMap<>());
227 }
228
229
230
231
232 public int getStatus() {
233 return status;
234 }
235
236
237
238
239
240
241
242 @Deprecated
243 public Date getRequestDate() {
244 return DateUtils.toDate(requestDate);
245 }
246
247
248
249
250
251
252
253 public Instant getRequestInstant() {
254 return requestDate;
255 }
256
257
258
259
260
261
262 @Deprecated
263 public Date getResponseDate() {
264 return DateUtils.toDate(responseDate);
265 }
266
267
268
269
270
271
272
273 public Instant getResponseInstant() {
274 return responseDate;
275 }
276
277
278
279
280 @Override
281 public Header[] getHeaders() {
282 return responseHeaders.getHeaders();
283 }
284
285
286
287
288
289 @Override
290 public Header getFirstHeader(final String name) {
291 return responseHeaders.getFirstHeader(name);
292 }
293
294
295
296
297 @Override
298 public Header getLastHeader(final String name) {
299 return responseHeaders.getLastHeader(name);
300 }
301
302
303
304
305
306 @Override
307 public Header[] getHeaders(final String name) {
308 return responseHeaders.getHeaders(name);
309 }
310
311
312
313
314 @Override
315 public boolean containsHeader(final String name) {
316 return responseHeaders.containsHeader(name);
317 }
318
319
320
321
322 @Override
323 public int countHeaders(final String name) {
324 return responseHeaders.countHeaders(name);
325 }
326
327
328
329
330 @Override
331 public Header getHeader(final String name) throws ProtocolException {
332 return responseHeaders.getHeader(name);
333 }
334
335
336
337
338 @Override
339 public Iterator<Header> headerIterator() {
340 return responseHeaders.headerIterator();
341 }
342
343
344
345
346 @Override
347 public Iterator<Header> headerIterator(final String name) {
348 return responseHeaders.headerIterator(name);
349 }
350
351
352
353
354 public MessageHeaders responseHeaders() {
355 return responseHeaders;
356 }
357
358
359
360
361
362
363
364 public Date getDate() {
365 return DateUtils.toDate(getInstant());
366 }
367
368 private static final Instant NON_VALUE = Instant.ofEpochSecond(Instant.MIN.getEpochSecond(), 0);
369
370 private Instant getInstant(final AtomicReference<Instant> ref, final String headerName) {
371 Instant instant = ref.get();
372 if (instant == null) {
373 instant = DateUtils.parseStandardDate(this, headerName);
374 if (instant == null) {
375 instant = NON_VALUE;
376 }
377 if (!ref.compareAndSet(null, instant)) {
378 instant = ref.get();
379 }
380 }
381 return instant != null && instant != NON_VALUE ? instant : null;
382 }
383
384
385
386
387 public Instant getInstant() {
388 return getInstant(dateRef, HttpHeaders.DATE);
389 }
390
391
392
393
394 public Instant getExpires() {
395 return getInstant(expiresRef, HttpHeaders.EXPIRES);
396 }
397
398
399
400
401 public Instant getLastModified() {
402 return getInstant(lastModifiedRef, HttpHeaders.LAST_MODIFIED);
403 }
404
405
406
407
408 public ETag getETag() {
409 ETag eTag = eTagRef.get();
410 if (eTag == null) {
411 eTag = ETag.get(this);
412 if (eTag == null) {
413 return null;
414 }
415 if (!eTagRef.compareAndSet(null, eTag)) {
416 eTag = eTagRef.get();
417 }
418 }
419 return eTag;
420 }
421
422
423
424
425 public Resource getResource() {
426 return this.resource;
427 }
428
429
430
431
432
433
434 public boolean hasVariants() {
435 return variants != null;
436 }
437
438
439
440
441
442
443 public Set<String> getVariants() {
444 return variants != null ? variants : Collections.emptySet();
445 }
446
447
448
449
450 @Deprecated
451 public Map<String, String> getVariantMap() {
452 return variants != null ? variants.stream()
453 .collect(Collectors.toMap(Function.identity(), e -> e + requestURI)) : Collections.emptyMap();
454 }
455
456
457
458
459
460
461
462 public String getRequestMethod() {
463 return method;
464 }
465
466
467
468
469 public String getRequestURI() {
470 return requestURI;
471 }
472
473
474
475
476 public MessageHeaders requestHeaders() {
477 return requestHeaders;
478 }
479
480
481
482
483 public Iterator<Header> requestHeaderIterator() {
484 return requestHeaders.headerIterator();
485 }
486
487
488
489
490 public Iterator<Header> requestHeaderIterator(final String headerName) {
491 return requestHeaders.headerIterator(headerName);
492 }
493
494
495
496
497
498
499
500
501 public static boolean isNewer(final HttpCacheEntry entry, final MessageHeaders message) {
502 if (entry == null || message == null) {
503 return false;
504 }
505 final Instant cacheDate = entry.getInstant();
506 if (cacheDate == null) {
507 return false;
508 }
509 final Instant messageDate = DateUtils.parseStandardDate(message, HttpHeaders.DATE);
510 if (messageDate == null) {
511 return false;
512 }
513 return cacheDate.compareTo(messageDate) > 0;
514 }
515
516
517
518
519
520 @Override
521 public String toString() {
522 return "HttpCacheEntry{" +
523 "requestDate=" + requestDate +
524 ", responseDate=" + responseDate +
525 ", method='" + method + '\'' +
526 ", requestURI='" + requestURI + '\'' +
527 ", requestHeaders=" + requestHeaders +
528 ", status=" + status +
529 ", responseHeaders=" + responseHeaders +
530 ", resource=" + resource +
531 ", variants=" + variants +
532 '}';
533 }
534 }