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