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.impl.cache;
28  
29  import java.util.Date;
30  import java.util.HashMap;
31  import java.util.Map;
32  
33  import org.apache.hc.client5.http.cache.HeaderConstants;
34  import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
35  import org.apache.hc.client5.http.cache.HttpCacheEntry;
36  import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
37  import org.apache.hc.client5.http.cache.HttpCacheStorage;
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.core5.http.Header;
42  import org.apache.hc.core5.http.HttpHost;
43  import org.apache.hc.core5.http.HttpRequest;
44  import org.apache.hc.core5.http.HttpResponse;
45  import org.apache.hc.core5.http.Method;
46  import org.apache.hc.core5.http.message.RequestLine;
47  import org.apache.hc.core5.http.message.StatusLine;
48  import org.apache.hc.core5.util.ByteArrayBuffer;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  class BasicHttpCache implements HttpCache {
53  
54      private static final Logger LOG = LoggerFactory.getLogger(BasicHttpCache.class);
55  
56      private final CacheUpdateHandler cacheUpdateHandler;
57      private final CacheKeyGenerator cacheKeyGenerator;
58      private final HttpCacheInvalidator cacheInvalidator;
59      private final HttpCacheStorage storage;
60  
61      public BasicHttpCache(
62              final ResourceFactory resourceFactory,
63              final HttpCacheStorage storage,
64              final CacheKeyGenerator cacheKeyGenerator,
65              final HttpCacheInvalidator cacheInvalidator) {
66          this.cacheUpdateHandler = new CacheUpdateHandler(resourceFactory);
67          this.cacheKeyGenerator = cacheKeyGenerator;
68          this.storage = storage;
69          this.cacheInvalidator = cacheInvalidator;
70      }
71  
72      public BasicHttpCache(
73              final ResourceFactory resourceFactory,
74              final HttpCacheStorage storage,
75              final CacheKeyGenerator cacheKeyGenerator) {
76          this(resourceFactory, storage, cacheKeyGenerator, new DefaultCacheInvalidator());
77      }
78  
79      public BasicHttpCache(final ResourceFactory resourceFactory, final HttpCacheStorage storage) {
80          this( resourceFactory, storage, new CacheKeyGenerator());
81      }
82  
83      public BasicHttpCache(final CacheConfig config) {
84          this(new HeapResourceFactory(), new BasicHttpCacheStorage(config));
85      }
86  
87      public BasicHttpCache() {
88          this(CacheConfig.DEFAULT);
89      }
90  
91      @Override
92      public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
93          if (cacheEntry == null) {
94              return cacheKeyGenerator.generateKey(host, request);
95          } else {
96              return cacheKeyGenerator.generateKey(host, request, cacheEntry);
97          }
98      }
99  
100     @Override
101     public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
102         if (LOG.isDebugEnabled()) {
103             LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
104         }
105         if (!Method.isSafe(request.getMethod())) {
106             final String cacheKey = cacheKeyGenerator.generateKey(host, request);
107             try {
108                 storage.removeEntry(cacheKey);
109             } catch (final ResourceIOException ex) {
110                 if (LOG.isWarnEnabled()) {
111                     LOG.warn("I/O error removing cache entry with key {}", cacheKey);
112                 }
113             }
114         }
115     }
116 
117     @Override
118     public void flushCacheEntriesInvalidatedByRequest(final HttpHost host, final HttpRequest request) {
119         if (LOG.isDebugEnabled()) {
120             LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
121         }
122         cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage);
123     }
124 
125     @Override
126     public void flushCacheEntriesInvalidatedByExchange(final HttpHost host, final HttpRequest request, final HttpResponse response) {
127         if (LOG.isDebugEnabled()) {
128             LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
129         }
130         if (!Method.isSafe(request.getMethod())) {
131             cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage);
132         }
133     }
134 
135     void storeInCache(
136             final String cacheKey,
137             final HttpHost host,
138             final HttpRequest request,
139             final HttpCacheEntry entry) {
140         if (entry.hasVariants()) {
141             storeVariantEntry(cacheKey, host, request, entry);
142         } else {
143             storeEntry(cacheKey, entry);
144         }
145     }
146 
147     void storeEntry(final String cacheKey, final HttpCacheEntry entry) {
148         try {
149             storage.putEntry(cacheKey, entry);
150         } catch (final ResourceIOException ex) {
151             if (LOG.isWarnEnabled()) {
152                 LOG.warn("I/O error storing cache entry with key {}", cacheKey);
153             }
154         }
155     }
156 
157     void storeVariantEntry(
158             final String cacheKey,
159             final HttpHost host,
160             final HttpRequest req,
161             final HttpCacheEntry entry) {
162         final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
163         final String variantCacheKey = cacheKeyGenerator.generateKey(host, req, entry);
164         storeEntry(variantCacheKey, entry);
165         try {
166             storage.updateEntry(cacheKey, new HttpCacheCASOperation() {
167 
168                 @Override
169                 public HttpCacheEntryache/HttpCacheEntry.html#HttpCacheEntry">HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
170                     return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey);
171                 }
172 
173             });
174         } catch (final HttpCacheUpdateException ex) {
175             if (LOG.isWarnEnabled()) {
176                 LOG.warn("Cannot update cache entry with key {}", cacheKey);
177             }
178         } catch (final ResourceIOException ex) {
179             if (LOG.isWarnEnabled()) {
180                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
181             }
182         }
183     }
184 
185     @Override
186     public void reuseVariantEntryFor(
187             final HttpHost host, final HttpRequest request, final Variant variant) {
188         if (LOG.isDebugEnabled()) {
189             LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), variant);
190         }
191         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
192         final HttpCacheEntry entry = variant.getEntry();
193         final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
194         final String variantCacheKey = variant.getCacheKey();
195 
196         try {
197             storage.updateEntry(cacheKey, new HttpCacheCASOperation() {
198 
199                 @Override
200                 public HttpCacheEntryache/HttpCacheEntry.html#HttpCacheEntry">HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
201                     return cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey);
202                 }
203 
204             });
205         } catch (final HttpCacheUpdateException ex) {
206             if (LOG.isWarnEnabled()) {
207                 LOG.warn("Cannot update cache entry with key {}", cacheKey);
208             }
209         } catch (final ResourceIOException ex) {
210             if (LOG.isWarnEnabled()) {
211                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
212             }
213         }
214     }
215 
216     @Override
217     public HttpCacheEntry updateCacheEntry(
218             final HttpHost host,
219             final HttpRequest request,
220             final HttpCacheEntry stale,
221             final HttpResponse originResponse,
222             final Date requestSent,
223             final Date responseReceived) {
224         if (LOG.isDebugEnabled()) {
225             LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
226         }
227         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
228         try {
229             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
230                     request.getRequestUri(),
231                     stale,
232                     requestSent,
233                     responseReceived,
234                     originResponse);
235             storeInCache(cacheKey, host, request, updatedEntry);
236             return updatedEntry;
237         } catch (final ResourceIOException ex) {
238             if (LOG.isWarnEnabled()) {
239                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
240             }
241             return stale;
242         }
243     }
244 
245     @Override
246     public HttpCacheEntry updateVariantCacheEntry(
247             final HttpHost host,
248             final HttpRequest request,
249             final HttpResponse originResponse,
250             final Variant variant,
251             final Date requestSent,
252             final Date responseReceived) {
253         if (LOG.isDebugEnabled()) {
254             LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), variant);
255         }
256         final HttpCacheEntry entry = variant.getEntry();
257         final String cacheKey = variant.getCacheKey();
258         try {
259             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
260                     request.getRequestUri(),
261                     entry,
262                     requestSent,
263                     responseReceived,
264                     originResponse);
265             storeEntry(cacheKey, updatedEntry);
266             return updatedEntry;
267         } catch (final ResourceIOException ex) {
268             if (LOG.isWarnEnabled()) {
269                 LOG.warn("I/O error updating cache entry with key {}", cacheKey);
270             }
271             return entry;
272         }
273     }
274 
275     @Override
276     public HttpCacheEntry createCacheEntry(
277             final HttpHost host,
278             final HttpRequest request,
279             final HttpResponse originResponse,
280             final ByteArrayBuffer content,
281             final Date requestSent,
282             final Date responseReceived) {
283         if (LOG.isDebugEnabled()) {
284             LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
285         }
286         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
287         try {
288             final HttpCacheEntry entry = cacheUpdateHandler.createtCacheEntry(request, originResponse, content, requestSent, responseReceived);
289             storeInCache(cacheKey, host, request, entry);
290             return entry;
291         } catch (final ResourceIOException ex) {
292             if (LOG.isWarnEnabled()) {
293                 LOG.warn("I/O error creating cache entry with key {}", cacheKey);
294             }
295             return new HttpCacheEntry(
296                     requestSent,
297                     responseReceived,
298                     originResponse.getCode(),
299                     originResponse.getHeaders(),
300                     content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
301         }
302     }
303 
304     @Override
305     public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) {
306         if (LOG.isDebugEnabled()) {
307             LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
308         }
309         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
310         final HttpCacheEntry root;
311         try {
312             root = storage.getEntry(cacheKey);
313         } catch (final ResourceIOException ex) {
314             if (LOG.isWarnEnabled()) {
315                 LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
316             }
317             return null;
318         }
319         if (root == null) {
320             return null;
321         }
322         if (!root.hasVariants()) {
323             return root;
324         }
325         final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
326         final String variantCacheKey = root.getVariantMap().get(variantKey);
327         if (variantCacheKey == null) {
328             return null;
329         }
330         try {
331             return storage.getEntry(variantCacheKey);
332         } catch (final ResourceIOException ex) {
333             if (LOG.isWarnEnabled()) {
334                 LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
335             }
336             return null;
337         }
338     }
339 
340     @Override
341     public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) {
342         if (LOG.isDebugEnabled()) {
343             LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
344         }
345         final Map<String,Variant> variants = new HashMap<>();
346         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
347         final HttpCacheEntry root;
348         try {
349             root = storage.getEntry(cacheKey);
350         } catch (final ResourceIOException ex) {
351             if (LOG.isWarnEnabled()) {
352                 LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
353             }
354             return variants;
355         }
356         if (root != null && root.hasVariants()) {
357             for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
358                 final String variantCacheKey = variant.getValue();
359                 try {
360                     final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
361                     if (entry != null) {
362                         final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
363                         if (etagHeader != null) {
364                             variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
365                         }
366                     }
367                 } catch (final ResourceIOException ex) {
368                     if (LOG.isWarnEnabled()) {
369                         LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
370                     }
371                     return variants;
372                 }
373             }
374         }
375         return variants;
376     }
377 
378 }