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