diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java index 0c7292cba..378251921 100644 --- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java +++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java @@ -149,7 +149,7 @@ public abstract class HttpCacheEntry implements Serializable { public abstract Resource getResource(); - public abstract InputStream getBody(); + public abstract InputStream getBody() throws IOException; public abstract long getBodyLength(); diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntryFactory.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntryFactory.java new file mode 100644 index 000000000..9dc24609a --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntryFactory.java @@ -0,0 +1,53 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.client.cache; + +import java.io.IOException; +import java.util.Date; + +import org.apache.http.Header; +import org.apache.http.StatusLine; + +/** + * Generates {@link HttpCacheEntry} instances. + * + * @since 4.1 + */ +public interface HttpCacheEntryFactory { + + HttpCacheEntry generate( + Date requestDate, + Date responseDate, + StatusLine statusLine, + Header[] headers, + byte[] body) throws IOException; + + HttpCacheEntry copyVariant( + HttpCacheEntry entry, + String variantURI) throws IOException; + +} diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java index 0319dfd0e..2a94c440b 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java @@ -69,7 +69,7 @@ class CacheEntity implements HttpEntity, Serializable { return this.cacheEntry.getBodyLength(); } - public InputStream getContent() { + public InputStream getContent() throws IOException { return this.cacheEntry.getBody(); } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryGenerator.java index 44998b772..70667a2d9 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryGenerator.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryGenerator.java @@ -33,17 +33,20 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import org.apache.http.Header; import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; import org.apache.http.annotation.Immutable; import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.HttpCacheEntryFactory; /** - * Generates a {@link CacheEntry} from a {@link HttpResponse} + * Generates {@link MemCacheEntry}s. * * @since 4.1 */ @Immutable -class CacheEntryGenerator { +class CacheEntryGenerator implements HttpCacheEntryFactory { public HttpCacheEntry generateEntry( Date requestDate, @@ -51,14 +54,28 @@ class CacheEntryGenerator { HttpResponse response, byte[] body) { return new MemCacheEntry(requestDate, - responseDate, - response.getStatusLine(), - response.getAllHeaders(), - body, - null); + responseDate, + response.getStatusLine(), + response.getAllHeaders(), + body, + null); } - public HttpCacheEntry copyWithVariant( + public HttpCacheEntry generate( + final Date requestDate, + final Date responseDate, + final StatusLine statusLine, + final Header[] headers, + byte[] body) throws IOException { + return new MemCacheEntry(requestDate, + responseDate, + statusLine, + headers, + body, + null); + } + + public HttpCacheEntry copyVariant( final HttpCacheEntry entry, final String variantURI) throws IOException { Set variants = new HashSet(entry.getVariantURIs()); variants.add(variantURI); diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java index 6bf987dde..a667de641 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java @@ -40,6 +40,7 @@ import org.apache.http.HttpResponse; import org.apache.http.annotation.Immutable; import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.HttpCacheEntryFactory; import org.apache.http.impl.cookie.DateParseException; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.protocol.HTTP; @@ -54,6 +55,17 @@ import org.apache.http.protocol.HTTP; @Immutable class CacheEntryUpdater { + private final HttpCacheEntryFactory cacheEntryFactory; + + CacheEntryUpdater() { + this(new CacheEntryGenerator()); + } + + CacheEntryUpdater(final HttpCacheEntryFactory cacheEntryFactory) { + super(); + this.cacheEntryFactory = cacheEntryFactory; + } + /** * Update the entry with the new information from the response. * @@ -78,11 +90,12 @@ class CacheEntryUpdater { while ((len = instream.read(buf)) != -1) { outstream.write(buf, 0, len); } - HttpCacheEntry updated = new MemCacheEntry(requestDate, responseDate, - entry.getStatusLine(), - mergedHeaders, - outstream.toByteArray(), - null); + HttpCacheEntry updated = cacheEntryFactory.generate( + requestDate, + responseDate, + entry.getStatusLine(), + mergedHeaders, + outstream.toByteArray()); return updated; } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java index e5dec67d7..33e777aac 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java @@ -50,6 +50,7 @@ import org.apache.http.client.ResponseHandler; import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HttpCache; import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.HttpCacheEntryFactory; import org.apache.http.client.cache.HttpCacheUpdateCallback; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; @@ -75,9 +76,9 @@ public class CachingHttpClient implements HttpClient { private final HttpClient backend; private final HttpCache responseCache; + private final HttpCacheEntryFactory cacheEntryFactory; private final CacheValidityPolicy validityPolicy; private final ResponseCachingPolicy responseCachingPolicy; - private final CacheEntryGenerator cacheEntryGenerator; private final URIExtractor uriExtractor; private final CachedHttpResponseGenerator responseGenerator; private final CacheInvalidator cacheInvalidator; @@ -96,7 +97,11 @@ public class CachingHttpClient implements HttpClient { private final Log log = LogFactory.getLog(getClass()); - public CachingHttpClient(HttpClient client, HttpCache cache, CacheConfig config) { + public CachingHttpClient( + HttpClient client, + HttpCache cache, + HttpCacheEntryFactory cacheEntryFactory, + CacheConfig config) { super(); if (client == null) { throw new IllegalArgumentException("HttpClient may not be null"); @@ -104,6 +109,9 @@ public class CachingHttpClient implements HttpClient { if (cache == null) { throw new IllegalArgumentException("HttpCache may not be null"); } + if (cacheEntryFactory == null) { + throw new IllegalArgumentException("HttpCacheEntryFactory may not be null"); + } if (config == null) { throw new IllegalArgumentException("CacheConfig may not be null"); } @@ -111,51 +119,81 @@ public class CachingHttpClient implements HttpClient { this.sharedCache = config.isSharedCache(); this.backend = client; this.responseCache = cache; + this.cacheEntryFactory = cacheEntryFactory; + this.validityPolicy = new CacheValidityPolicy(); this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache); this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy); - this.cacheEntryGenerator = new CacheEntryGenerator(); this.uriExtractor = new URIExtractor(); this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache); this.cacheableRequestPolicy = new CacheableRequestPolicy(); this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy); this.conditionalRequestBuilder = new ConditionalRequestBuilder(); - this.cacheEntryUpdater = new CacheEntryUpdater(); + this.cacheEntryUpdater = new CacheEntryUpdater(this.cacheEntryFactory); this.responseCompliance = new ResponseProtocolCompliance(); this.requestCompliance = new RequestProtocolCompliance(); } public CachingHttpClient() { - this(new DefaultHttpClient(), new BasicHttpCache(MAX_CACHE_ENTRIES), new CacheConfig()); + this(new DefaultHttpClient(), + new BasicHttpCache(MAX_CACHE_ENTRIES), + new CacheEntryGenerator(), + new CacheConfig()); } public CachingHttpClient(CacheConfig config) { - this(new DefaultHttpClient(), new BasicHttpCache(MAX_CACHE_ENTRIES), config); + this(new DefaultHttpClient(), + new BasicHttpCache(MAX_CACHE_ENTRIES), + new CacheEntryGenerator(), + config); } public CachingHttpClient(HttpClient client) { - this(client, new BasicHttpCache(MAX_CACHE_ENTRIES), new CacheConfig()); + this(client, + new BasicHttpCache(MAX_CACHE_ENTRIES), + new CacheEntryGenerator(), + new CacheConfig()); } public CachingHttpClient(HttpClient client, CacheConfig config) { - this(client, new BasicHttpCache(MAX_CACHE_ENTRIES), config); + this(client, + new BasicHttpCache(MAX_CACHE_ENTRIES), + new CacheEntryGenerator(), + config); } - public CachingHttpClient(HttpCache cache) { - this(new DefaultHttpClient(), cache, new CacheConfig()); + public CachingHttpClient( + HttpCache cache, + HttpCacheEntryFactory cacheEntryFactory) { + this(new DefaultHttpClient(), + cache, + cacheEntryFactory, + new CacheConfig()); } - public CachingHttpClient(HttpCache cache, CacheConfig config) { - this(new DefaultHttpClient(), cache, config); + public CachingHttpClient( + HttpCache cache, + HttpCacheEntryFactory cacheEntryFactory, + CacheConfig config) { + this(new DefaultHttpClient(), + cache, + cacheEntryFactory, + config); } - public CachingHttpClient(HttpClient client, HttpCache cache) { - this(client, cache, new CacheConfig()); + public CachingHttpClient( + HttpClient client, + HttpCache cache, + HttpCacheEntryFactory cacheEntryFactory) { + this(client, + cache, + cacheEntryFactory, + new CacheConfig()); } CachingHttpClient(HttpClient backend, CacheValidityPolicy validityPolicy, ResponseCachingPolicy responseCachingPolicy, - CacheEntryGenerator cacheEntryGenerator, URIExtractor uriExtractor, + HttpCacheEntryFactory cacheEntryFactory, URIExtractor uriExtractor, HttpCache responseCache, CachedHttpResponseGenerator responseGenerator, CacheInvalidator cacheInvalidator, CacheableRequestPolicy cacheableRequestPolicy, CachedResponseSuitabilityChecker suitabilityChecker, @@ -166,9 +204,9 @@ public class CachingHttpClient implements HttpClient { this.maxObjectSizeBytes = config.getMaxObjectSizeBytes(); this.sharedCache = config.isSharedCache(); this.backend = backend; + this.cacheEntryFactory = cacheEntryFactory; this.validityPolicy = validityPolicy; this.responseCachingPolicy = responseCachingPolicy; - this.cacheEntryGenerator = cacheEntryGenerator; this.uriExtractor = uriExtractor; this.responseCache = responseCache; this.responseGenerator = responseGenerator; @@ -532,9 +570,9 @@ public class CachingHttpClient implements HttpClient { HttpCacheEntry existing, HttpCacheEntry entry, String variantURI) throws IOException { if (existing != null) { - return cacheEntryGenerator.copyWithVariant(existing, variantURI); + return cacheEntryFactory.copyVariant(existing, variantURI); } else { - return cacheEntryGenerator.copyWithVariant(entry, variantURI); + return cacheEntryFactory.copyVariant(entry, variantURI); } } @@ -590,9 +628,12 @@ public class CachingHttpClient implements HttpClient { responseBytes); int correctedStatus = corrected.getStatusLine().getStatusCode(); if (HttpStatus.SC_BAD_GATEWAY != correctedStatus) { - HttpCacheEntry entry = cacheEntryGenerator - .generateEntry(requestDate, responseDate, corrected, - responseBytes); + HttpCacheEntry entry = cacheEntryFactory.generate( + requestDate, + responseDate, + corrected.getStatusLine(), + corrected.getAllHeaders(), + responseBytes); storeInCache(target, request, entry); return responseGenerator.generateResponse(entry); } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileCacheEntry.java new file mode 100644 index 000000000..60420d003 --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileCacheEntry.java @@ -0,0 +1,98 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.impl.client.cache; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Set; + +import org.apache.http.Header; +import org.apache.http.StatusLine; +import org.apache.http.annotation.Immutable; +import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.Resource; + +/** + * {@link File} backed {@link HttpCacheEntry} that requires explicit deallocation. + */ +@Immutable +class FileCacheEntry extends HttpCacheEntry { + + private static final long serialVersionUID = -8396589100351931966L; + + private final File file; + private final FileResource resource; + + public FileCacheEntry( + final Date requestDate, + final Date responseDate, + final StatusLine statusLine, + final Header[] responseHeaders, + final File file, + final Set variants) { + super(requestDate, responseDate, statusLine, responseHeaders, variants); + this.file = file; + this.resource = new FileResource(file); + } + + @Override + public long getBodyLength() { + return this.file.length(); + } + + @Override + public InputStream getBody() throws IOException { + return new FileInputStream(this.file); + } + + @Override + public Resource getResource() { + return this.resource; + } + + class FileResource implements Resource { + + private File file; + + FileResource(final File file) { + super(); + this.file = file; + } + + public synchronized void dispose() { + if (this.file != null) { + this.file.delete(); + this.file = null; + } + } + + } + +} diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java new file mode 100644 index 000000000..cac9f5439 --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java @@ -0,0 +1,171 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.impl.client.cache; + +import java.io.IOException; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.HashSet; +import java.util.Set; + +import org.apache.http.annotation.ThreadSafe; +import org.apache.http.client.cache.HttpCache; +import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.HttpCacheUpdateCallback; +import org.apache.http.client.cache.Resource; + +/** + * {@link HttpCache} implementation capable of deallocating resources associated with + * the cache entries. This cache keeps track of cache entries using {@link PhantomReference} + * and maintains a collection of all resources that are no longer in use. The cache, however, + * does not automatically deallocates associated resources by invoking {@link Resource#dispose()} + * method. The consumer MUST periodically call {@link #cleanResources()} method to trigger + * resource deallocation. The cache can be permanently shut down using {@link #shutdown()} + * method. All resources associated with the entries used by the cache will be deallocated. + * + * This {@link HttpCache} implementation is intended for use with {@link FileCacheEntry} + * and similar. + * + * @since 4.1 + */ +@ThreadSafe +public class ManagedHttpCache implements HttpCache { + + private final CacheMap entries; + private final ReferenceQueue morque; + private final Set resources; + + private volatile boolean shutdown; + + public ManagedHttpCache(int maxEntries) { + super(); + this.entries = new CacheMap(maxEntries); + this.morque = new ReferenceQueue(); + this.resources = new HashSet(); + } + + private void ensureValidState() throws IllegalStateException { + if (this.shutdown) { + throw new IllegalStateException("Cache has been shut down"); + } + } + + private void keepResourceReference(final HttpCacheEntry entry) { + Resource resource = entry.getResource(); + if (resource != null) { + // Must deallocate the resource when the entry is no longer in used + ResourceReference ref = new ResourceReference(entry, this.morque); + this.resources.add(ref); + } + } + + public void putEntry(final String url, final HttpCacheEntry entry) throws IOException { + if (url == null) { + throw new IllegalArgumentException("URL may not be null"); + } + if (entry == null) { + throw new IllegalArgumentException("Cache entry may not be null"); + } + ensureValidState(); + synchronized (this) { + this.entries.put(url, entry); + keepResourceReference(entry); + } + } + + public HttpCacheEntry getEntry(final String url) throws IOException { + if (url == null) { + throw new IllegalArgumentException("URL may not be null"); + } + ensureValidState(); + synchronized (this) { + return this.entries.get(url); + } + } + + public void removeEntry(String url) throws IOException { + if (url == null) { + throw new IllegalArgumentException("URL may not be null"); + } + ensureValidState(); + synchronized (this) { + // Cannot deallocate the associated resources immediately as the + // cache entry may still be in use + this.entries.remove(url); + } + } + + public void updateEntry( + final String url, + final HttpCacheUpdateCallback callback) throws IOException { + if (url == null) { + throw new IllegalArgumentException("URL may not be null"); + } + if (callback == null) { + throw new IllegalArgumentException("Callback may not be null"); + } + ensureValidState(); + synchronized (this) { + HttpCacheEntry existing = this.entries.get(url); + HttpCacheEntry updated = callback.update(existing); + this.entries.put(url, updated); + if (existing != updated) { + keepResourceReference(updated); + } + } + } + + public void cleanResources() { + if (this.shutdown) { + return; + } + ResourceReference ref; + while ((ref = (ResourceReference) this.morque.poll()) != null) { + synchronized (this) { + this.resources.remove(ref); + } + ref.getResource().dispose(); + } + } + + public void shutdown() { + if (this.shutdown) { + return; + } + this.shutdown = true; + synchronized (this) { + this.entries.clear(); + for (ResourceReference ref: this.resources) { + ref.getResource().dispose(); + } + this.resources.clear(); + while (this.morque.poll() != null) { + } + } + } + +} diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/MemCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/MemCacheEntry.java index 4b809bdc2..9bcd06c79 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/MemCacheEntry.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/MemCacheEntry.java @@ -42,7 +42,7 @@ import org.apache.http.client.cache.Resource; * explicit deallocation. */ @Immutable -public class MemCacheEntry extends HttpCacheEntry { +class MemCacheEntry extends HttpCacheEntry { private static final long serialVersionUID = -8464486112875881235L; diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResourceReference.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResourceReference.java new file mode 100644 index 000000000..91e9a0b16 --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResourceReference.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.impl.client.cache; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +import org.apache.http.annotation.Immutable; +import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.cache.Resource; + +@Immutable +class ResourceReference extends PhantomReference { + + private final Resource resource; + + public ResourceReference(final HttpCacheEntry entry, final ReferenceQueue q) { + super(entry, q); + if (entry.getResource() == null) { + throw new IllegalArgumentException("Resource may not be null"); + } + this.resource = entry.getResource(); + } + + public Resource getResource() { + return this.resource; + } + + @Override + public int hashCode() { + return this.resource.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return this.resource.equals(obj); + } + +} diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java index d0d9f14b3..62b4b0471 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java @@ -10,6 +10,7 @@ import org.apache.http.HttpStatus; import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.cache.HttpCache; +import org.apache.http.client.cache.HttpCacheEntryFactory; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.BasicHttpResponse; @@ -32,7 +33,8 @@ public abstract class AbstractProtocolTest { protected HttpResponse originResponse; protected CacheConfig params; protected CachingHttpClient impl; - private HttpCache cache; + protected HttpCache cache; + protected HttpCacheEntryFactory cacheEntryFactory; public static HttpRequest eqRequest(HttpRequest in) { EasyMock.reportMatcher(new RequestEquivalent(in)); @@ -50,12 +52,13 @@ public abstract class AbstractProtocolTest { originResponse = make200Response(); cache = new BasicHttpCache(MAX_ENTRIES); + cacheEntryFactory = new CacheEntryGenerator(); mockBackend = EasyMock.createMock(HttpClient.class); mockEntity = EasyMock.createMock(HttpEntity.class); mockCache = EasyMock.createMock(HttpCache.class); params = new CacheConfig(); params.setMaxObjectSizeBytes(MAX_BYTES); - impl = new CachingHttpClient(mockBackend, cache, params); + impl = new CachingHttpClient(mockBackend, cache, cacheEntryFactory, params); } protected void replayMocks() { @@ -94,7 +97,7 @@ public abstract class AbstractProtocolTest { mockCache = EasyMock.createMock(HttpCache.class); mockEntity = EasyMock.createMock(HttpEntity.class); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); EasyMock.expect(mockCache.getEntry((String) EasyMock.anyObject())).andReturn(null) .anyTimes(); @@ -105,7 +108,7 @@ public abstract class AbstractProtocolTest { protected void behaveAsNonSharedCache() { params.setSharedCache(false); - impl = new CachingHttpClient(mockBackend, cache, params); + impl = new CachingHttpClient(mockBackend, cache, cacheEntryFactory, params); } public AbstractProtocolTest() { diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java index 36d8383af..47cfc4967 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java @@ -82,7 +82,7 @@ public class DoNotTestProtocolRequirements { mockCache = EasyMock.createMock(HttpCache.class); CacheConfig params = new CacheConfig(); params.setMaxObjectSizeBytes(MAX_BYTES); - impl = new CachingHttpClient(mockBackend, cache, params); + impl = new CachingHttpClient(mockBackend, cache, new CacheEntryGenerator(), params); } private HttpResponse make200Response() { diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntry.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntry.java index 055a6f41a..9a1d584de 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntry.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntry.java @@ -110,8 +110,8 @@ public class TestCacheEntry { CacheEntryGenerator entryGenerator = new CacheEntryGenerator(); - HttpCacheEntry addedOne = entryGenerator.copyWithVariant(entry, "foo"); - HttpCacheEntry addedTwo = entryGenerator.copyWithVariant(addedOne, "bar"); + HttpCacheEntry addedOne = entryGenerator.copyVariant(entry, "foo"); + HttpCacheEntry addedTwo = entryGenerator.copyVariant(addedOne, "bar"); Set variants = addedTwo.getVariantURIs(); diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java index 9dfa6a1d9..7b98f3ff5 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java @@ -26,7 +26,6 @@ */ package org.apache.http.impl.client.cache; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -40,8 +39,8 @@ import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; import org.apache.http.ProtocolException; -import org.apache.http.ProtocolVersion; import org.apache.http.RequestLine; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; @@ -49,16 +48,10 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.cache.HttpCache; import org.apache.http.client.cache.HttpCacheEntry; -import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.cache.HttpCacheEntryFactory; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import org.apache.http.params.HttpParams; @@ -66,13 +59,10 @@ import org.apache.http.protocol.HttpContext; import org.easymock.classextension.EasyMock; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class TestCachingHttpClient { - private static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP",1,1); - private static final String GET_CURRENT_DATE = "getCurrentDate"; private static final String HANDLE_BACKEND_RESPONSE = "handleBackendResponse"; @@ -87,6 +77,9 @@ public class TestCachingHttpClient { private static final String GET_RESPONSE_READER = "getResponseReader"; + private static final StatusLine SC_OK = new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK"); + private static final Header[] HEADERS = new Header[] {}; + private CachingHttpClient impl; private boolean mockedImpl; @@ -103,7 +96,7 @@ public class TestCachingHttpClient { private CacheEntry mockVariantCacheEntry; private CacheEntry mockUpdatedCacheEntry; private URIExtractor mockExtractor; - private CacheEntryGenerator mockEntryGenerator; + private HttpCacheEntryFactory mockEntryGenerator; private CachedHttpResponseGenerator mockResponseGenerator; private SizeLimitedResponseReader mockResponseReader; @@ -151,7 +144,7 @@ public class TestCachingHttpClient { mockUpdatedCacheEntry = EasyMock.createMock(CacheEntry.class); mockVariantCacheEntry = EasyMock.createMock(CacheEntry.class); mockExtractor = EasyMock.createMock(URIExtractor.class); - mockEntryGenerator = EasyMock.createMock(CacheEntryGenerator.class); + mockEntryGenerator = EasyMock.createMock(HttpCacheEntryFactory.class); mockResponseGenerator = EasyMock.createMock(CachedHttpResponseGenerator.class); mockCachedResponse = EasyMock.createMock(HttpResponse.class); mockConditionalRequestBuilder = EasyMock.createMock(ConditionalRequestBuilder.class); @@ -259,6 +252,7 @@ public class TestCachingHttpClient { storeInCacheWasCalled(); responseIsGeneratedFromCache(); responseStatusLineIsInspectable(); + responseGetHeaders(); responseDoesNotHaveExplicitContentLength(); replayMocks(); @@ -602,6 +596,7 @@ public class TestCachingHttpClient { storeInCacheWasCalled(); responseIsGeneratedFromCache(); responseStatusLineIsInspectable(); + responseGetHeaders(); responseDoesNotHaveExplicitContentLength(); replayMocks(); @@ -863,34 +858,6 @@ public class TestCachingHttpClient { Assert.assertSame(mockParams, result); } - @Test - @Ignore - public void testRealResultsMatch() throws IOException { - - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); - schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); - - ClientConnectionManager cm = new ThreadSafeClientConnManager(schemeRegistry); - HttpClient httpClient = new DefaultHttpClient(cm); - - HttpCache cacheImpl = new BasicHttpCache(100); - - CachingHttpClient cachingClient = new CachingHttpClient(httpClient, cacheImpl); - - HttpUriRequest request = new HttpGet("http://www.fancast.com/static-28262/styles/base.css"); - - HttpClient baseClient = new DefaultHttpClient(); - - HttpResponse cachedResponse = cachingClient.execute(request); - HttpResponse realResponse = baseClient.execute(request); - - byte[] cached = readResponse(cachedResponse); - byte[] real = readResponse(realResponse); - - Assert.assertArrayEquals(cached, real); - } - @Test public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception { @@ -936,7 +903,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseDoesNotCorrectComplete200Response() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","128"); @@ -948,7 +915,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseDoesNotCorrectComplete206Response() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT, "Partial Content"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT, "Partial Content"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","128"); @@ -961,7 +928,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseGenerates502ForIncomplete200Response() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); @@ -973,7 +940,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseDoesNotCorrectIncompleteNon200Or206Responses() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_FORBIDDEN, "Forbidden"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_FORBIDDEN, "Forbidden"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); @@ -985,7 +952,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseDoesNotCorrectResponsesWithoutExplicitContentLength() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); @@ -996,7 +963,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseDoesNotCorrectResponsesWithUnparseableContentLengthHeader() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setHeader("Content-Length","foo"); resp.setEntity(new ByteArrayEntity(bytes)); @@ -1008,7 +975,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseProvidesPlainTextErrorMessage() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); @@ -1021,7 +988,7 @@ public class TestCachingHttpClient { @Test public void testCorrectIncompleteResponseProvidesNonEmptyErrorMessage() throws Exception { - HttpResponse resp = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK"); + HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); @@ -1048,18 +1015,6 @@ public class TestCachingHttpClient { Assert.assertTrue(impl.isSharedCache()); } - private byte[] readResponse(HttpResponse response) { - try { - ByteArrayOutputStream s1 = new ByteArrayOutputStream(); - response.getEntity().writeTo(s1); - return s1.toByteArray(); - } catch (Exception ex) { - return new byte[]{}; - - } - - } - private void cacheInvalidatorWasCalled() throws IOException { mockInvalidator.flushInvalidatedCacheEntries(host, mockRequest); } @@ -1182,9 +1137,11 @@ public class TestCachingHttpClient { } private void responseStatusLineIsInspectable() { - StatusLine statusLine = new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "OK"); - EasyMock.expect(mockBackendResponse.getStatusLine()) - .andReturn(statusLine).anyTimes(); + EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(SC_OK).anyTimes(); + } + + private void responseGetHeaders() { + EasyMock.expect(mockBackendResponse.getAllHeaders()).andReturn(HEADERS).anyTimes(); } private void responseIsGeneratedFromCache(CacheEntry entry) { @@ -1214,15 +1171,15 @@ public class TestCachingHttpClient { mockCache.putEntry(theURI, entry); } - private void generateCacheEntry(Date requestDate, Date responseDate, byte[] bytes) { + private void generateCacheEntry(Date requestDate, Date responseDate, byte[] bytes) throws IOException { EasyMock.expect( - mockEntryGenerator.generateEntry(requestDate, responseDate, mockBackendResponse, - bytes)).andReturn(mockCacheEntry); + mockEntryGenerator.generate(requestDate, responseDate, SC_OK, HEADERS, + bytes)).andReturn(mockCacheEntry); } private void copyCacheEntry(CacheEntry entry, String variantURI) throws IOException { EasyMock.expect( - mockEntryGenerator.copyWithVariant(entry, variantURI)).andReturn(entry); + mockEntryGenerator.copyVariant(entry, variantURI)).andReturn(entry); } private void handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response) diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java index d6a7b2619..b8b3ddee2 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java @@ -100,7 +100,7 @@ public class TestProtocolDeviations { CacheConfig params = new CacheConfig(); params.setMaxObjectSizeBytes(MAX_BYTES); - impl = new CachingHttpClient(mockBackend, cache, params); + impl = new CachingHttpClient(mockBackend, cache, new CacheEntryGenerator(), params); } private HttpResponse make200Response() { diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java index f3a45853a..a15013fba 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java @@ -2221,7 +2221,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest { mockCache.putEntry(EasyMock.eq("http://foo.example.com/thing"), EasyMock.isA(HttpCacheEntry.class)); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); HttpRequest validate = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1); validate.setHeader("If-None-Match", "\"etag\""); @@ -2263,7 +2263,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest { CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); @@ -2304,7 +2304,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest { CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); EasyMock.expect( @@ -2505,7 +2505,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest { CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); @@ -2549,7 +2549,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest { CacheEntry entry = new CacheEntry(requestTime, responseTime, hdrs, bytes); - impl = new CachingHttpClient(mockBackend, mockCache, params); + impl = new CachingHttpClient(mockBackend, mockCache, cacheEntryFactory, params); HttpResponse validated = make200Response(); validated.setHeader("Cache-Control", "public");