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.net.URI;
30  
31  import org.apache.hc.client5.http.cache.HttpCacheEntry;
32  import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
33  import org.apache.hc.client5.http.cache.HttpCacheStorage;
34  import org.apache.hc.client5.http.cache.ResourceIOException;
35  import org.apache.hc.client5.http.utils.URIUtils;
36  import org.apache.hc.core5.annotation.Contract;
37  import org.apache.hc.core5.annotation.Internal;
38  import org.apache.hc.core5.annotation.ThreadingBehavior;
39  import org.apache.hc.core5.function.Resolver;
40  import org.apache.hc.core5.http.Header;
41  import org.apache.hc.core5.http.HttpHeaders;
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.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * Given a particular HTTP request / response pair, flush any cache entries
50   * that this exchange would invalidate.
51   *
52   * @since 4.1
53   */
54  @Contract(threading = ThreadingBehavior.STATELESS)
55  @Internal
56  public class DefaultCacheInvalidator extends CacheInvalidatorBase implements HttpCacheInvalidator {
57  
58      public static final DefaultCacheInvalidator INSTANCE = new DefaultCacheInvalidator();
59  
60      private static final Logger LOG = LoggerFactory.getLogger(DefaultCacheInvalidator.class);
61  
62      private HttpCacheEntry getEntry(final HttpCacheStorage storage, final String cacheKey) {
63          try {
64              return storage.getEntry(cacheKey);
65          } catch (final ResourceIOException ex) {
66              if (LOG.isWarnEnabled()) {
67                  LOG.warn("Unable to get cache entry with key {}", cacheKey, ex);
68              }
69              return null;
70          }
71      }
72  
73      private void removeEntry(final HttpCacheStorage storage, final String cacheKey) {
74          try {
75              storage.removeEntry(cacheKey);
76          } catch (final ResourceIOException ex) {
77              if (LOG.isWarnEnabled()) {
78                  LOG.warn("Unable to flush cache entry with key {}", cacheKey, ex);
79              }
80          }
81      }
82  
83      @Override
84      public void flushCacheEntriesInvalidatedByRequest(
85              final HttpHost host,
86              final HttpRequest request,
87              final Resolver<URI, String> cacheKeyResolver,
88              final HttpCacheStorage storage) {
89          final String s = HttpCacheSupport.getRequestUri(request, host);
90          final URI uri = HttpCacheSupport.normalizeQuietly(s);
91          final String cacheKey = uri != null ? cacheKeyResolver.resolve(uri) : s;
92          final HttpCacheEntry parent = getEntry(storage, cacheKey);
93  
94          if (requestShouldNotBeCached(request) || shouldInvalidateHeadCacheEntry(request, parent)) {
95              if (parent != null) {
96                  if (LOG.isDebugEnabled()) {
97                      LOG.debug("Invalidating parent cache entry with key {}", cacheKey);
98                  }
99                  for (final String variantURI : parent.getVariantMap().values()) {
100                     removeEntry(storage, variantURI);
101                 }
102                 removeEntry(storage, cacheKey);
103             }
104             if (uri != null) {
105                 if (LOG.isWarnEnabled()) {
106                     LOG.warn("{} is not a valid URI", s);
107                 }
108                 final Header clHdr = request.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
109                 if (clHdr != null) {
110                     final URI contentLocation = HttpCacheSupport.normalizeQuietly(clHdr.getValue());
111                     if (contentLocation != null) {
112                         if (!flushAbsoluteUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage)) {
113                             flushRelativeUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage);
114                         }
115                     }
116                 }
117                 final Header lHdr = request.getFirstHeader(HttpHeaders.LOCATION);
118                 if (lHdr != null) {
119                     final URI location = HttpCacheSupport.normalizeQuietly(lHdr.getValue());
120                     if (location != null) {
121                         flushAbsoluteUriFromSameHost(uri, location, cacheKeyResolver, storage);
122                     }
123                 }
124             }
125         }
126     }
127 
128     private void flushRelativeUriFromSameHost(
129             final URI requestUri,
130             final URI uri,
131             final Resolver<URI, String> cacheKeyResolver,
132             final HttpCacheStorage storage) {
133         final URI resolvedUri = uri != null ? URIUtils.resolve(requestUri, uri) : null;
134         if (resolvedUri != null && isSameHost(requestUri, resolvedUri)) {
135             removeEntry(storage, cacheKeyResolver.resolve(resolvedUri));
136         }
137     }
138 
139     private boolean flushAbsoluteUriFromSameHost(
140             final URI requestUri,
141             final URI uri,
142             final Resolver<URI, String> cacheKeyResolver,
143             final HttpCacheStorage storage) {
144         if (uri != null && isSameHost(requestUri, uri)) {
145             removeEntry(storage, cacheKeyResolver.resolve(uri));
146             return true;
147         }
148         return false;
149     }
150 
151     @Override
152     public void flushCacheEntriesInvalidatedByExchange(
153             final HttpHost host,
154             final HttpRequest request,
155             final HttpResponse response,
156             final Resolver<URI, String> cacheKeyResolver,
157             final HttpCacheStorage storage) {
158         final int status = response.getCode();
159         if (status < 200 || status > 299) {
160             return;
161         }
162         final String s = HttpCacheSupport.getRequestUri(request, host);
163         final URI uri = HttpCacheSupport.normalizeQuietly(s);
164         if (uri == null) {
165             return;
166         }
167         final URI contentLocation = getContentLocationURI(uri, response);
168         if (contentLocation != null && isSameHost(uri, contentLocation)) {
169             flushLocationCacheEntry(response, contentLocation, storage, cacheKeyResolver);
170         }
171         final URI location = getLocationURI(uri, response);
172         if (location != null && isSameHost(uri, location)) {
173             flushLocationCacheEntry(response, location, storage, cacheKeyResolver);
174         }
175     }
176 
177     private void flushLocationCacheEntry(
178             final HttpResponse response,
179             final URI location,
180             final HttpCacheStorage storage,
181             final Resolver<URI, String> cacheKeyResolver) {
182         final String cacheKey = cacheKeyResolver.resolve(location);
183         final HttpCacheEntry entry = getEntry(storage, cacheKey);
184         if (entry != null) {
185             // do not invalidate if response is strictly older than entry
186             // or if the etags match
187 
188             if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
189                 removeEntry(storage, cacheKey);
190             }
191         }
192     }
193 
194 }