1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import java.net.URI;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33
34 import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
35 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
36 import org.apache.hc.client5.http.cache.HttpCacheEntry;
37 import org.apache.hc.client5.http.impl.Operations;
38 import org.apache.hc.client5.http.utils.URIUtils;
39 import org.apache.hc.core5.annotation.Contract;
40 import org.apache.hc.core5.annotation.Internal;
41 import org.apache.hc.core5.annotation.ThreadingBehavior;
42 import org.apache.hc.core5.concurrent.Cancellable;
43 import org.apache.hc.core5.concurrent.FutureCallback;
44 import org.apache.hc.core5.function.Resolver;
45 import org.apache.hc.core5.http.Header;
46 import org.apache.hc.core5.http.HttpHeaders;
47 import org.apache.hc.core5.http.HttpHost;
48 import org.apache.hc.core5.http.HttpRequest;
49 import org.apache.hc.core5.http.HttpResponse;
50 import org.apache.hc.core5.http.HttpStatus;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54
55
56
57
58
59
60 @Contract(threading = ThreadingBehavior.STATELESS)
61 @Internal
62 public class DefaultAsyncCacheInvalidator extends CacheInvalidatorBase implements HttpAsyncCacheInvalidator {
63
64 public static final DefaultAsyncCacheInvalidator INSTANCE = new DefaultAsyncCacheInvalidator();
65
66 private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncCacheInvalidator.class);
67
68 private void removeEntry(final HttpAsyncCacheStorage storage, final String cacheKey) {
69 storage.removeEntry(cacheKey, new FutureCallback<Boolean>() {
70
71 @Override
72 public void completed(final Boolean result) {
73 if (LOG.isDebugEnabled()) {
74 if (result.booleanValue()) {
75 LOG.debug("Cache entry with key {} successfully flushed", cacheKey);
76 } else {
77 LOG.debug("Cache entry with key {} could not be flushed", cacheKey);
78 }
79 }
80 }
81
82 @Override
83 public void failed(final Exception ex) {
84 if (LOG.isWarnEnabled()) {
85 LOG.warn("Unable to flush cache entry with key {}", cacheKey, ex);
86 }
87 }
88
89 @Override
90 public void cancelled() {
91 }
92
93 });
94 }
95
96 @Override
97 public Cancellable flushCacheEntriesInvalidatedByRequest(
98 final HttpHost host,
99 final HttpRequest request,
100 final Resolver<URI, String> cacheKeyResolver,
101 final HttpAsyncCacheStorage storage,
102 final FutureCallback<Boolean> callback) {
103 final String s = HttpCacheSupport.getRequestUri(request, host);
104 final URI uri = HttpCacheSupport.normalizeQuietly(s);
105 final String cacheKey = uri != null ? cacheKeyResolver.resolve(uri) : s;
106 return storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
107
108 @Override
109 public void completed(final HttpCacheEntry parentEntry) {
110 if (requestShouldNotBeCached(request) || shouldInvalidateHeadCacheEntry(request, parentEntry)) {
111 if (parentEntry != null) {
112 if (LOG.isDebugEnabled()) {
113 LOG.debug("Invalidating parentEntry cache entry with key {}", cacheKey);
114 }
115 for (final String variantURI : parentEntry.getVariantMap().values()) {
116 removeEntry(storage, variantURI);
117 }
118 removeEntry(storage, cacheKey);
119 }
120 if (uri != null) {
121 if (LOG.isWarnEnabled()) {
122 LOG.warn("{} is not a valid URI", s);
123 }
124 final Header clHdr = request.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
125 if (clHdr != null) {
126 final URI contentLocation = HttpCacheSupport.normalizeQuietly(clHdr.getValue());
127 if (contentLocation != null) {
128 if (!flushAbsoluteUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage)) {
129 flushRelativeUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage);
130 }
131 }
132 }
133 final Header lHdr = request.getFirstHeader(HttpHeaders.LOCATION);
134 if (lHdr != null) {
135 final URI location = HttpCacheSupport.normalizeQuietly(lHdr.getValue());
136 if (location != null) {
137 flushAbsoluteUriFromSameHost(uri, location, cacheKeyResolver, storage);
138 }
139 }
140 }
141 }
142 callback.completed(Boolean.TRUE);
143 }
144
145 @Override
146 public void failed(final Exception ex) {
147 callback.failed(ex);
148 }
149
150 @Override
151 public void cancelled() {
152 callback.cancelled();
153 }
154
155 });
156
157 }
158
159 private void flushRelativeUriFromSameHost(
160 final URI requestUri,
161 final URI uri,
162 final Resolver<URI, String> cacheKeyResolver,
163 final HttpAsyncCacheStorage storage) {
164 final URI resolvedUri = uri != null ? URIUtils.resolve(requestUri, uri) : null;
165 if (resolvedUri != null && isSameHost(requestUri, resolvedUri)) {
166 removeEntry(storage, cacheKeyResolver.resolve(resolvedUri));
167 }
168 }
169
170 private boolean flushAbsoluteUriFromSameHost(
171 final URI requestUri,
172 final URI uri,
173 final Resolver<URI, String> cacheKeyResolver,
174 final HttpAsyncCacheStorage storage) {
175 if (uri != null && isSameHost(requestUri, uri)) {
176 removeEntry(storage, cacheKeyResolver.resolve(uri));
177 return true;
178 }
179 return false;
180 }
181
182 @Override
183 public Cancellable flushCacheEntriesInvalidatedByExchange(
184 final HttpHost host,
185 final HttpRequest request,
186 final HttpResponse response,
187 final Resolver<URI, String> cacheKeyResolver,
188 final HttpAsyncCacheStorage storage,
189 final FutureCallback<Boolean> callback) {
190 final int status = response.getCode();
191 if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
192 final String s = HttpCacheSupport.getRequestUri(request, host);
193 final URI requestUri = HttpCacheSupport.normalizeQuietly(s);
194 if (requestUri != null) {
195 final List<String> cacheKeys = new ArrayList<>(2);
196 final URI contentLocation = getContentLocationURI(requestUri, response);
197 if (contentLocation != null && isSameHost(requestUri, contentLocation)) {
198 cacheKeys.add(cacheKeyResolver.resolve(contentLocation));
199 }
200 final URI location = getLocationURI(requestUri, response);
201 if (location != null && isSameHost(requestUri, location)) {
202 cacheKeys.add(cacheKeyResolver.resolve(location));
203 }
204 if (cacheKeys.size() == 1) {
205 final String key = cacheKeys.get(0);
206 storage.getEntry(key, new FutureCallback<HttpCacheEntry>() {
207
208 @Override
209 public void completed(final HttpCacheEntry entry) {
210 if (entry != null) {
211
212
213 if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
214 removeEntry(storage, key);
215 }
216 }
217 callback.completed(Boolean.TRUE);
218 }
219
220 @Override
221 public void failed(final Exception ex) {
222 callback.failed(ex);
223 }
224
225 @Override
226 public void cancelled() {
227 callback.cancelled();
228 }
229
230 });
231 } else if (cacheKeys.size() > 1) {
232 storage.getEntries(cacheKeys, new FutureCallback<Map<String, HttpCacheEntry>>() {
233
234 @Override
235 public void completed(final Map<String, HttpCacheEntry> resultMap) {
236 for (final Map.Entry<String, HttpCacheEntry> resultEntry: resultMap.entrySet()) {
237
238
239 final String key = resultEntry.getKey();
240 final HttpCacheEntry entry = resultEntry.getValue();
241 if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
242 removeEntry(storage, key);
243 }
244 }
245 callback.completed(Boolean.TRUE);
246 }
247
248 @Override
249 public void failed(final Exception ex) {
250 callback.failed(ex);
251 }
252
253 @Override
254 public void cancelled() {
255 callback.cancelled();
256 }
257
258 });
259 }
260 }
261 }
262 callback.completed(Boolean.TRUE);
263 return Operations.nonCancellable();
264 }
265
266 }