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.cache;
28  
29  import java.io.Serializable;
30  import java.time.Instant;
31  import java.util.Collections;
32  import java.util.Date;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.Map;
36  
37  import org.apache.hc.client5.http.utils.DateUtils;
38  import org.apache.hc.core5.annotation.Contract;
39  import org.apache.hc.core5.annotation.ThreadingBehavior;
40  import org.apache.hc.core5.http.Header;
41  import org.apache.hc.core5.http.HttpHeaders;
42  import org.apache.hc.core5.http.HttpStatus;
43  import org.apache.hc.core5.http.MessageHeaders;
44  import org.apache.hc.core5.http.ProtocolException;
45  import org.apache.hc.core5.http.message.HeaderGroup;
46  import org.apache.hc.core5.util.Args;
47  
48  /**
49   * Structure used to store an {@link org.apache.hc.core5.http.HttpResponse} in a cache.
50   * Some entries can optionally depend on system resources that may require
51   * explicit deallocation. In such a case {@link #getResource()} should return
52   * a non null instance of {@link Resource} that must be deallocated by calling
53   * {@link Resource#dispose()} method when no longer used.
54   *
55   * @since 4.1
56   */
57  @Contract(threading = ThreadingBehavior.IMMUTABLE)
58  public class HttpCacheEntry implements MessageHeaders, Serializable {
59  
60      private static final long serialVersionUID = -6300496422359477413L;
61      private static final String REQUEST_METHOD_HEADER_NAME = "Hc-Request-Method";
62  
63      private final Instant requestDate;
64      private final Instant responseDate;
65      private final int status;
66      private final HeaderGroup responseHeaders;
67      private final Resource resource;
68      private final Map<String, String> variantMap;
69      private final Instant date;
70  
71      /**
72       * Create a new {@link HttpCacheEntry} with variants.
73       * @param requestDate
74       *          Date/time when the request was made (Used for age
75       *            calculations)
76       * @param responseDate
77       *          Date/time that the response came back (Used for age
78       *            calculations)
79       * @param status
80       *          HTTP status from origin response
81       * @param responseHeaders
82       *          Header[] from original HTTP Response
83       * @param resource representing origin response body
84       * @param variantMap describing cache entries that are variants
85       *   of this parent entry; this maps a "variant key" (derived
86       *   from the varying request headers) to a "cache key" (where
87       *   in the cache storage the particular variant is located)
88       * @deprecated  Use {{@link #HttpCacheEntry(Instant, Instant, int, Header[], Resource, Map)}}
89       */
90      @Deprecated
91      public HttpCacheEntry(
92              final Date requestDate,
93              final Date responseDate,
94              final int status,
95              final Header[] responseHeaders,
96              final Resource resource,
97              final Map<String, String> variantMap) {
98          super();
99          Args.notNull(requestDate, "Request date");
100         Args.notNull(responseDate, "Response date");
101         Args.check(status >= HttpStatus.SC_SUCCESS, "Status code");
102         Args.notNull(responseHeaders, "Response headers");
103         this.requestDate = DateUtils.toInstant(requestDate);
104         this.responseDate = DateUtils.toInstant(responseDate);
105         this.status = status;
106         this.responseHeaders = new HeaderGroup();
107         this.responseHeaders.setHeaders(responseHeaders);
108         this.resource = resource;
109         this.variantMap = variantMap != null ? new HashMap<>(variantMap) : null;
110         this.date = parseDate();
111     }
112 
113     /**
114      * Create a new {@link HttpCacheEntry} with variants.
115      *
116      * @param requestDate     Date/time when the request was made (Used for age calculations)
117      * @param responseDate    Date/time that the response came back (Used for age calculations)
118      * @param status          HTTP status from origin response
119      * @param responseHeaders Header[] from original HTTP Response
120      * @param resource        representing origin response body
121      * @param variantMap      describing cache entries that are variants of this parent entry; this
122      *                        maps a "variant key" (derived from the varying request headers) to a
123      *                        "cache key" (where in the cache storage the particular variant is
124      *                        located)
125      * @since 5.2
126      */
127     public HttpCacheEntry(
128             final Instant requestDate,
129             final Instant responseDate,
130             final int status,
131             final Header[] responseHeaders,
132             final Resource resource,
133             final Map<String, String> variantMap) {
134         super();
135         Args.notNull(requestDate, "Request date");
136         Args.notNull(responseDate, "Response date");
137         Args.check(status >= HttpStatus.SC_SUCCESS, "Status code");
138         Args.notNull(responseHeaders, "Response headers");
139         this.requestDate = requestDate;
140         this.responseDate = responseDate;
141         this.status = status;
142         this.responseHeaders = new HeaderGroup();
143         this.responseHeaders.setHeaders(responseHeaders);
144         this.resource = resource;
145         this.variantMap = variantMap != null ? new HashMap<>(variantMap) : null;
146         this.date = parseDate();
147     }
148 
149     /**
150      * Create a new {@link HttpCacheEntry}.
151      *
152      * @param requestDate     Date/time when the request was made (Used for age calculations)
153      * @param responseDate    Date/time that the response came back (Used for age calculations)
154      * @param status          HTTP status from origin response
155      * @param responseHeaders Header[] from original HTTP Response
156      * @param resource        representing origin response body
157      * @deprecated {{@link #HttpCacheEntry(Instant, Instant, int, Header[], Resource)}}
158      */
159     @Deprecated
160     public HttpCacheEntry(final Date requestDate, final Date responseDate, final int status,
161                           final Header[] responseHeaders, final Resource resource) {
162         this(requestDate, responseDate, status, responseHeaders, resource, new HashMap<>());
163     }
164     /**
165      * Create a new {@link HttpCacheEntry}.
166      *
167      * @param requestDate
168      *          Date/time when the request was made (Used for age
169      *            calculations)
170      * @param responseDate
171      *          Date/time that the response came back (Used for age
172      *            calculations)
173      * @param status
174      *          HTTP status from origin response
175      * @param responseHeaders
176      *          Header[] from original HTTP Response
177      * @param resource representing origin response body
178      */
179     public HttpCacheEntry(final Instant requestDate, final Instant responseDate, final int status,
180                           final Header[] responseHeaders, final Resource resource) {
181         this(requestDate, responseDate, status, responseHeaders, resource, new HashMap<>());
182     }
183 
184     /**
185      * Find the "Date" response header and parse it into a {@link Instant}
186      * @return the Date value of the header or null if the header is not present
187      */
188     private Instant parseDate() {
189         return DateUtils.parseStandardDate(this, HttpHeaders.DATE);
190     }
191 
192     /**
193      * Returns the status from the origin {@link org.apache.hc.core5.http.HttpResponse}.
194      */
195     public int getStatus() {
196         return this.status;
197     }
198 
199     /**
200      * Returns the time the associated origin request was initiated by the
201      * caching module.
202      * @return {@link Date}
203      * @deprecated USe {@link #getRequestInstant()}
204      */
205     @Deprecated
206     public Date getRequestDate() {
207         return DateUtils.toDate(requestDate);
208     }
209 
210     /**
211      * Returns the time the associated origin request was initiated by the
212      * caching module.
213      * @return {@link Instant}
214      * @since 5.2
215      */
216     public Instant getRequestInstant() {
217         return requestDate;
218     }
219 
220     /**
221      * Returns the time the origin response was received by the caching module.
222      * @return {@link Date}
223      * @deprecated  Use {@link #getResponseInstant()}
224      */
225     @Deprecated
226     public Date getResponseDate() {
227         return DateUtils.toDate(responseDate);
228     }
229 
230     /**
231      * Returns the time the origin response was received by the caching module.
232      *
233      * @return {@link Instant}
234      * @since 5.2
235      */
236     public Instant getResponseInstant() {
237         return responseDate;
238     }
239 
240     /**
241      * Returns all the headers that were on the origin response.
242      */
243     @Override
244     public Header[] getHeaders() {
245         final HeaderGroup filteredHeaders = new HeaderGroup();
246         for (final Iterator<Header> iterator = responseHeaders.headerIterator(); iterator.hasNext();) {
247             final Header header = iterator.next();
248             if (!REQUEST_METHOD_HEADER_NAME.equals(header.getName())) {
249                 filteredHeaders.addHeader(header);
250             }
251         }
252         return filteredHeaders.getHeaders();
253     }
254 
255     /**
256      * Returns the first header from the origin response with the given
257      * name.
258      */
259     @Override
260     public Header getFirstHeader(final String name) {
261         if (REQUEST_METHOD_HEADER_NAME.equalsIgnoreCase(name)) {
262             return null;
263         }
264         return responseHeaders.getFirstHeader(name);
265     }
266 
267     /**
268      * @since 5.0
269      */
270     @Override
271     public Header getLastHeader(final String name) {
272         return responseHeaders.getLastHeader(name);
273     }
274 
275     /**
276      * Gets all the headers with the given name that were on the origin
277      * response.
278      */
279     @Override
280     public Header[] getHeaders(final String name) {
281         if (REQUEST_METHOD_HEADER_NAME.equalsIgnoreCase(name)) {
282             return new Header[0];
283         }
284         return responseHeaders.getHeaders(name);
285     }
286 
287     /**
288      * @since 5.0
289      */
290     @Override
291     public boolean containsHeader(final String name) {
292         return responseHeaders.containsHeader(name);
293     }
294 
295     /**
296      * @since 5.0
297      */
298     @Override
299     public int countHeaders(final String name) {
300         return responseHeaders.countHeaders(name);
301     }
302 
303     /**
304      * @since 5.0
305      */
306     @Override
307     public Header getHeader(final String name) throws ProtocolException {
308         return responseHeaders.getHeader(name);
309     }
310 
311     /**
312      * @since 5.0
313      */
314     @Override
315     public Iterator<Header> headerIterator() {
316         return responseHeaders.headerIterator();
317     }
318 
319     /**
320      * @since 5.0
321      */
322     @Override
323     public Iterator<Header> headerIterator(final String name) {
324         return responseHeaders.headerIterator(name);
325     }
326 
327     /**
328      * Gets the Date value of the "Date" header or null if the header is missing or cannot be
329      * parsed.
330      *
331      * @since 4.3
332      */
333     public Date getDate() {
334         return DateUtils.toDate(date);
335     }
336 
337     public Instant getInstant() {
338         return date;
339     }
340 
341     /**
342      * Returns the {@link Resource} containing the origin response body.
343      */
344     public Resource getResource() {
345         return this.resource;
346     }
347 
348     /**
349      * Indicates whether the origin response indicated the associated
350      * resource had variants (i.e. that the Vary header was set on the
351      * origin response).
352      * @return {@code true} if this cached response was a variant
353      */
354     public boolean hasVariants() {
355         return getFirstHeader(HeaderConstants.VARY) != null;
356     }
357 
358     /**
359      * Returns an index about where in the cache different variants for
360      * a given resource are stored. This maps "variant keys" to "cache keys",
361      * where the variant key is derived from the varying request headers,
362      * and the cache key is the location in the
363      * {@link HttpCacheStorage} where that
364      * particular variant is stored. The first variant returned is used as
365      * the "parent" entry to hold this index of the other variants.
366      */
367     public Map<String, String> getVariantMap() {
368         return Collections.unmodifiableMap(variantMap);
369     }
370 
371     /**
372      * Returns the HTTP request method that was used to create the cached
373      * response entry.
374      *
375      * @since 4.4
376      */
377     public String getRequestMethod() {
378         final Header requestMethodHeader = responseHeaders.getFirstHeader(REQUEST_METHOD_HEADER_NAME);
379         if (requestMethodHeader != null) {
380             return requestMethodHeader.getValue();
381         }
382         return HeaderConstants.GET_METHOD;
383     }
384 
385     /**
386      * Provides a string representation of this instance suitable for
387      * human consumption.
388      */
389     @Override
390     public String toString() {
391         return "[request date=" + this.requestDate + "; response date=" + this.responseDate
392                 + "; status=" + this.status + "]";
393     }
394 
395 }