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  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.http.Header;
34  import org.apache.http.HttpException;
35  import org.apache.http.HttpResponse;
36  import org.apache.http.client.cache.HeaderConstants;
37  import org.apache.http.client.cache.HttpCacheEntry;
38  import org.apache.http.client.methods.HttpExecutionAware;
39  import org.apache.http.client.methods.HttpRequestWrapper;
40  import org.apache.http.client.methods.CloseableHttpResponse;
41  import org.apache.http.client.protocol.HttpClientContext;
42  import org.apache.http.conn.routing.HttpRoute;
43  
44  /**
45   * Class used to represent an asynchronous revalidation event, such as with
46   * "stale-while-revalidate"
47   */
48  public class AsynchronousValidationRequest implements Runnable {
49      private final AsynchronousValidator parent;
50      private final CachingExec cachingExec;
51      private final HttpRoute route;
52      private final HttpRequestWrapper request;
53      private final HttpClientContext context;
54      private final HttpExecutionAware execAware;
55      private final HttpCacheEntry cacheEntry;
56      private final String identifier;
57      private final int consecutiveFailedAttempts;
58  
59      private final Log log = LogFactory.getLog(getClass());
60  
61      /**
62       * Used internally by {@link AsynchronousValidator} to schedule a
63       * revalidation.
64       * @param request
65       * @param context
66       * @param cacheEntry
67       * @param identifier
68       * @param consecutiveFailedAttempts
69       */
70      AsynchronousValidationRequest(
71              final AsynchronousValidator parent,
72              final CachingExec cachingExec,
73              final HttpRoute route,
74              final HttpRequestWrapper request,
75              final HttpClientContext context,
76              final HttpExecutionAware execAware,
77              final HttpCacheEntry cacheEntry,
78              final String identifier,
79              final int consecutiveFailedAttempts) {
80          this.parent = parent;
81          this.cachingExec = cachingExec;
82          this.route = route;
83          this.request = request;
84          this.context = context;
85          this.execAware = execAware;
86          this.cacheEntry = cacheEntry;
87          this.identifier = identifier;
88          this.consecutiveFailedAttempts = consecutiveFailedAttempts;
89      }
90  
91      @Override
92      public void run() {
93          try {
94              if (revalidateCacheEntry()) {
95                  parent.jobSuccessful(identifier);
96              } else {
97                  parent.jobFailed(identifier);
98              }
99          } finally {
100             parent.markComplete(identifier);
101         }
102     }
103 
104     /**
105      * Revalidate the cache entry and return if the operation was successful.
106      * Success means a connection to the server was established and replay did
107      * not indicate a server error.
108      * @return {@code true} if the cache entry was successfully validated;
109      * otherwise {@code false}
110      */
111     private boolean revalidateCacheEntry() {
112         try {
113             final CloseableHttpResponse httpResponse = cachingExec.revalidateCacheEntry(route, request, context, execAware, cacheEntry);
114             try {
115                 final int statusCode = httpResponse.getStatusLine().getStatusCode();
116                 return isNotServerError(statusCode) && isNotStale(httpResponse);
117             } finally {
118                 httpResponse.close();
119             }
120         } catch (final IOException ioe) {
121             log.debug("Asynchronous revalidation failed due to I/O error", ioe);
122             return false;
123         } catch (final HttpException pe) {
124             log.error("HTTP protocol exception during asynchronous revalidation", pe);
125             return false;
126         } catch (final RuntimeException re) {
127             log.error("RuntimeException thrown during asynchronous revalidation: " + re);
128             return false;
129         }
130     }
131 
132     /**
133      * Return whether the status code indicates a server error or not.
134      * @param statusCode the status code to be checked
135      * @return if the status code indicates a server error or not
136      */
137     private boolean isNotServerError(final int statusCode) {
138         return statusCode < 500;
139     }
140 
141     /**
142      * Try to detect if the returned response is generated from a stale cache entry.
143      * @param httpResponse the response to be checked
144      * @return whether the response is stale or not
145      */
146     private boolean isNotStale(final HttpResponse httpResponse) {
147         final Header[] warnings = httpResponse.getHeaders(HeaderConstants.WARNING);
148         if (warnings != null)
149         {
150             for (final Header warning : warnings)
151             {
152                 /**
153                  * warn-codes
154                  * 110 = Response is stale
155                  * 111 = Revalidation failed
156                  */
157                 final String warningValue = warning.getValue();
158                 if (warningValue.startsWith("110") || warningValue.startsWith("111"))
159                 {
160                     return false;
161                 }
162             }
163         }
164         return true;
165     }
166 
167     public String getIdentifier() {
168         return identifier;
169     }
170 
171     /**
172      * The number of consecutively failed revalidation attempts.
173      * @return the number of consecutively failed revalidation attempts.
174      */
175     public int getConsecutiveFailedAttempts() {
176         return consecutiveFailedAttempts;
177     }
178 
179 }