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.http.impl.client.cache;
28  
29  import java.io.IOException;
30  import java.net.MalformedURLException;
31  import java.net.URL;
32  import java.util.Date;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.http.Header;
37  import org.apache.http.HttpHost;
38  import org.apache.http.HttpRequest;
39  import org.apache.http.HttpResponse;
40  import org.apache.http.annotation.Contract;
41  import org.apache.http.annotation.ThreadingBehavior;
42  import org.apache.http.client.cache.HeaderConstants;
43  import org.apache.http.client.cache.HttpCacheEntry;
44  import org.apache.http.client.cache.HttpCacheInvalidator;
45  import org.apache.http.client.cache.HttpCacheStorage;
46  import org.apache.http.client.utils.DateUtils;
47  import org.apache.http.protocol.HTTP;
48  
49  /**
50   * Given a particular HttpRequest, flush any cache entries that this request
51   * would invalidate.
52   *
53   * @since 4.1
54   */
55  @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
56  class CacheInvalidator implements HttpCacheInvalidator {
57  
58      private final HttpCacheStorage storage;
59      private final CacheKeyGenerator cacheKeyGenerator;
60  
61      private final Log log = LogFactory.getLog(getClass());
62  
63      /**
64       * Create a new {@link CacheInvalidator} for a given {@link HttpCache} and
65       * {@link CacheKeyGenerator}.
66       *
67       * @param uriExtractor Provides identifiers for the keys to store cache entries
68       * @param storage the cache to store items away in
69       */
70      public CacheInvalidator(
71              final CacheKeyGenerator uriExtractor,
72              final HttpCacheStorage storage) {
73          this.cacheKeyGenerator = uriExtractor;
74          this.storage = storage;
75      }
76  
77      /**
78       * Remove cache entries from the cache that are no longer fresh or
79       * have been invalidated in some way.
80       *
81       * @param host The backend host we are talking to
82       * @param req The HttpRequest to that host
83       */
84      @Override
85      public void flushInvalidatedCacheEntries(final HttpHost host, final HttpRequest req)  {
86          final String theUri = cacheKeyGenerator.getURI(host, req);
87          final HttpCacheEntry parent = getEntry(theUri);
88  
89          if (requestShouldNotBeCached(req) || shouldInvalidateHeadCacheEntry(req, parent)) {
90              log.debug("Invalidating parent cache entry: " + parent);
91              if (parent != null) {
92                  for (final String variantURI : parent.getVariantMap().values()) {
93                      flushEntry(variantURI);
94                  }
95                  flushEntry(theUri);
96              }
97              final URL reqURL = getAbsoluteURL(theUri);
98              if (reqURL == null) {
99                  log.error("Couldn't transform request into valid URL");
100                 return;
101             }
102             final Header clHdr = req.getFirstHeader("Content-Location");
103             if (clHdr != null) {
104                 final String contentLocation = clHdr.getValue();
105                 if (!flushAbsoluteUriFromSameHost(reqURL, contentLocation)) {
106                     flushRelativeUriFromSameHost(reqURL, contentLocation);
107                 }
108             }
109             final Header lHdr = req.getFirstHeader("Location");
110             if (lHdr != null) {
111                 flushAbsoluteUriFromSameHost(reqURL, lHdr.getValue());
112             }
113         }
114     }
115 
116     private boolean shouldInvalidateHeadCacheEntry(final HttpRequest req, final HttpCacheEntry parentCacheEntry) {
117         return requestIsGet(req) && isAHeadCacheEntry(parentCacheEntry);
118     }
119 
120     private boolean requestIsGet(final HttpRequest req) {
121         return req.getRequestLine().getMethod().equals((HeaderConstants.GET_METHOD));
122     }
123 
124     private boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) {
125         return parentCacheEntry != null && parentCacheEntry.getRequestMethod().equals(HeaderConstants.HEAD_METHOD);
126     }
127 
128     private void flushEntry(final String uri) {
129         try {
130             storage.removeEntry(uri);
131         } catch (final IOException ioe) {
132             log.warn("unable to flush cache entry", ioe);
133         }
134     }
135 
136     private HttpCacheEntry getEntry(final String theUri) {
137         try {
138             return storage.getEntry(theUri);
139         } catch (final IOException ioe) {
140             log.warn("could not retrieve entry from storage", ioe);
141         }
142         return null;
143     }
144 
145     protected void flushUriIfSameHost(final URL requestURL, final URL targetURL) {
146         final URL canonicalTarget = getAbsoluteURL(cacheKeyGenerator.canonicalizeUri(targetURL.toString()));
147         if (canonicalTarget == null) {
148             return;
149         }
150         if (canonicalTarget.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
151             flushEntry(canonicalTarget.toString());
152         }
153     }
154 
155     protected void flushRelativeUriFromSameHost(final URL reqURL, final String relUri) {
156         final URL relURL = getRelativeURL(reqURL, relUri);
157         if (relURL == null) {
158             return;
159         }
160         flushUriIfSameHost(reqURL, relURL);
161     }
162 
163 
164     protected boolean flushAbsoluteUriFromSameHost(final URL reqURL, final String uri) {
165         final URL absURL = getAbsoluteURL(uri);
166         if (absURL == null) {
167             return false;
168         }
169         flushUriIfSameHost(reqURL,absURL);
170         return true;
171     }
172 
173     private URL getAbsoluteURL(final String uri) {
174         URL absURL = null;
175         try {
176             absURL = new URL(uri);
177         } catch (final MalformedURLException mue) {
178             // nop
179         }
180         return absURL;
181     }
182 
183     private URL getRelativeURL(final URL reqURL, final String relUri) {
184         URL relURL = null;
185         try {
186             relURL = new URL(reqURL,relUri);
187         } catch (final MalformedURLException e) {
188             // nop
189         }
190         return relURL;
191     }
192 
193     protected boolean requestShouldNotBeCached(final HttpRequest req) {
194         final String method = req.getRequestLine().getMethod();
195         return notGetOrHeadRequest(method);
196     }
197 
198     private boolean notGetOrHeadRequest(final String method) {
199         return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
200                 .equals(method));
201     }
202 
203     /** Flushes entries that were invalidated by the given response
204      * received for the given host/request pair.
205      */
206     @Override
207     public void flushInvalidatedCacheEntries(final HttpHost host,
208             final HttpRequest request, final HttpResponse response) {
209         final int status = response.getStatusLine().getStatusCode();
210         if (status < 200 || status > 299) {
211             return;
212         }
213         final URL reqURL = getAbsoluteURL(cacheKeyGenerator.getURI(host, request));
214         if (reqURL == null) {
215             return;
216         }
217         final URL contentLocation = getContentLocationURL(reqURL, response);
218         if (contentLocation != null) {
219             flushLocationCacheEntry(reqURL, response, contentLocation);
220         }
221         final URL location = getLocationURL(reqURL, response);
222         if (location != null) {
223             flushLocationCacheEntry(reqURL, response, location);
224         }
225     }
226 
227     private void flushLocationCacheEntry(final URL reqURL,
228             final HttpResponse response, final URL location) {
229         final String cacheKey = cacheKeyGenerator.canonicalizeUri(location.toString());
230         final HttpCacheEntry entry = getEntry(cacheKey);
231         if (entry == null) {
232             return;
233         }
234 
235         // do not invalidate if response is strictly older than entry
236         // or if the etags match
237 
238         if (responseDateOlderThanEntryDate(response, entry)) {
239             return;
240         }
241         if (!responseAndEntryEtagsDiffer(response, entry)) {
242             return;
243         }
244 
245         flushUriIfSameHost(reqURL, location);
246     }
247 
248     private URL getContentLocationURL(final URL reqURL, final HttpResponse response) {
249         final Header clHeader = response.getFirstHeader("Content-Location");
250         if (clHeader == null) {
251             return null;
252         }
253         final String contentLocation = clHeader.getValue();
254         final URL canonURL = getAbsoluteURL(contentLocation);
255         if (canonURL != null) {
256             return canonURL;
257         }
258         return getRelativeURL(reqURL, contentLocation);
259     }
260 
261     private URL getLocationURL(final URL reqURL, final HttpResponse response) {
262         final Header clHeader = response.getFirstHeader("Location");
263         if (clHeader == null) {
264             return null;
265         }
266         final String location = clHeader.getValue();
267         final URL canonURL = getAbsoluteURL(location);
268         if (canonURL != null) {
269             return canonURL;
270         }
271         return getRelativeURL(reqURL, location);
272     }
273 
274     private boolean responseAndEntryEtagsDiffer(final HttpResponse response,
275             final HttpCacheEntry entry) {
276         final Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG);
277         final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG);
278         if (entryEtag == null || responseEtag == null) {
279             return false;
280         }
281         return (!entryEtag.getValue().equals(responseEtag.getValue()));
282     }
283 
284     private boolean responseDateOlderThanEntryDate(final HttpResponse response,
285             final HttpCacheEntry entry) {
286         final Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
287         final Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
288         if (entryDateHeader == null || responseDateHeader == null) {
289             /* be conservative; should probably flush */
290             return false;
291         }
292         final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
293         final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
294         if (entryDate == null || responseDate == null) {
295             return false;
296         }
297         return responseDate.before(entryDate);
298     }
299 }