Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryUpdater.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryUpdater.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryUpdater.java (revision ) @@ -26,32 +26,48 @@ */ package org.apache.http.client.cache.impl; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.ListIterator; - import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.annotation.Immutable; import org.apache.http.impl.cookie.DateParseException; import org.apache.http.impl.cookie.DateUtils; +import java.util.*; + /** + * Update a {@link CacheEntry} with new or updated information based on the latest + * 200 or 304 status responses from the Server. Use the {@link HttpResponse} to perform + * the update. + * * @since 4.1 */ @Immutable public class CacheEntryUpdater { - public void updateCacheEntry(CacheEntry entry, Date requestDate, Date responseDate, - HttpResponse response) { - entry.setRequestDate(requestDate); - entry.setResponseDate(responseDate); - mergeHeaders(entry, response); + /** + * Update the entry with the new information from the response. + * + * @param entry The cache Entry to be updated + * @param requestDate When the request was performed + * @param responseDate When the response was gotten + * @param response The HttpResponse from the backend server call + * @return CacheEntry an updated version of the cache entry + */ + public CacheEntry updateCacheEntry(CacheEntry entry, Date requestDate, Date responseDate, HttpResponse response) { + + Header[] mergedHeaders = mergeHeaders(entry, response); + + CacheEntry updated = new CacheEntry(requestDate, responseDate, + entry.getProtocolVersion(), + mergedHeaders, + entry.getBody(), + entry.getStatusCode(), + entry.getReasonPhrase()); + + return updated; } - protected void mergeHeaders(CacheEntry entry, HttpResponse response) { + protected Header[] mergeHeaders(CacheEntry entry, HttpResponse response) { List
cacheEntryHeaderList = new ArrayList
(Arrays.asList(entry .getAllHeaders())); @@ -60,7 +76,7 @@ // Don't merge Headers, keep the entries headers as they are newer. removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry); - return; + return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]); } removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response); @@ -68,8 +84,7 @@ cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders())); removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry); - entry.setResponseHeaders(cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList - .size()])); + return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]); } private void removeCacheHeadersThatMatchResponse(List
cacheEntryHeaderList, Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java (revision ) @@ -44,6 +44,12 @@ @Immutable public class ResponseProtocolCompliance { + /** + * + * @param request + * @param response + * @throws ClientProtocolException + */ public void ensureProtocolCompliance(HttpRequest request, HttpResponse response) throws ClientProtocolException { if (backendResponseMustNotHaveBody(request, response)) { Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java (revision ) @@ -26,17 +26,7 @@ */ package org.apache.http.client.cache.impl; -import java.util.ArrayList; -import java.util.List; - -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolException; -import org.apache.http.ProtocolVersion; +import org.apache.http.*; import org.apache.http.annotation.Immutable; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.impl.client.RequestWrapper; @@ -44,12 +34,20 @@ import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; +import java.util.ArrayList; +import java.util.List; + /** * @since 4.1 */ @Immutable public class RequestProtocolCompliance { + /** + * + * @param request + * @return + */ public List requestIsFatallyNonCompliant(HttpRequest request) { List theErrors = new ArrayList(); @@ -71,6 +69,12 @@ return theErrors; } + /** + * + * @param request + * @return + * @throws ProtocolException + */ public HttpRequest makeRequestCompliant(HttpRequest request) throws ProtocolException { if (requestMustNotHaveEntity(request)) { ((HttpEntityEnclosingRequest) request).setEntity(null); @@ -250,11 +254,11 @@ return null; } - Header range = request.getFirstHeader("Range"); + Header range = request.getFirstHeader(HeaderConstants.RANGE); if (range == null) return null; - Header ifRange = request.getFirstHeader("If-Range"); + Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE); if (ifRange == null) return null; @@ -267,8 +271,7 @@ } private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(HttpRequest request) { - // TODO: Should these be looking at all the headers marked as - // If-Match/If-None-Match? + // TODO: Should these be looking at all the headers marked as If-Match/If-None-Match? String method = request.getRequestLine().getMethod(); if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD @@ -276,14 +279,14 @@ return null; } - Header ifMatch = request.getFirstHeader("If-Match"); + Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH); if (ifMatch != null) { String val = ifMatch.getValue(); if (val.startsWith("W/")) { return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR; } } else { - Header ifNoneMatch = request.getFirstHeader("If-None-Match"); + Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH); if (ifNoneMatch == null) return null; Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java (revision 939843) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java (revision ) @@ -35,6 +35,7 @@ import org.apache.http.HttpStatus; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; +import org.apache.http.annotation.Immutable; import org.apache.http.message.AbstractHttpMessage; import org.apache.http.message.BasicStatusLine; import org.apache.http.params.BasicHttpParams; @@ -43,11 +44,12 @@ /** * @since 4.1 */ +@Immutable public final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse { - StatusLine statusLine = new BasicStatusLine(CachingHttpClient.HTTP_1_1, + private final StatusLine statusLine = new BasicStatusLine(CachingHttpClient.HTTP_1_1, HttpStatus.SC_NOT_IMPLEMENTED, ""); - ProtocolVersion version = CachingHttpClient.HTTP_1_1; + private final ProtocolVersion version = CachingHttpClient.HTTP_1_1; public StatusLine getStatusLine() { return statusLine; Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntry.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntry.java (revision 939814) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntry.java (revision ) @@ -26,83 +26,92 @@ */ package org.apache.http.client.cache.impl; +import org.apache.http.*; +import org.apache.http.annotation.Immutable; +import org.apache.http.impl.cookie.DateParseException; +import org.apache.http.impl.cookie.DateUtils; +import org.apache.http.message.BasicHeader; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; +import java.util.*; -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.ProtocolVersion; -import org.apache.http.StatusLine; -import org.apache.http.impl.cookie.DateParseException; -import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.message.BasicHeader; - /** * Structure used to store an {@link HttpResponse} in a cache * * @since 4.1 */ +@Immutable public class CacheEntry implements Serializable { private static final long serialVersionUID = -6300496422359477413L; public static final long MAX_AGE = 2147483648L; - private transient Header[] responseHeaders; - private byte[] body; - private ProtocolVersion version; - private int status; - private String reason; - private Date requestDate; - private Date responseDate; - private Set variantURIs = new HashSet(); + private final Date requestDate; + private final Date responseDate; + private final ProtocolVersion version; + private final int status; + private final String reason; + private final CachedHeaderGroup responseHeaders = new CachedHeaderGroup(); + private final byte[] body; + private final Set variantURIs = new HashSet(); /** - * Default constructor - */ - public CacheEntry() { - } - - /** + * * @param requestDate - * Date/time when the request was made (Used for age + * Date/time when the request was made (Used for age * calculations) * @param responseDate - * Date/time that the response came back (Used for age + * Date/time that the response came back (Used for age * calculations) - * @param response - * original {@link HttpResponse} + * @param version + * HTTP Response Version + * @param responseHeaders + * Header[] from original HTTP Response * @param responseBytes - * Byte array containing the body of the response + * Byte array containing the body of the response - * @throws IOException - * Does not attempt to handle IOExceptions + * @param status + * Numeric HTTP Status Code + * @param reason + * String message from HTTP Status Line */ - public CacheEntry(Date requestDate, Date responseDate, HttpResponse response, - byte[] responseBytes) throws IOException { + public CacheEntry(Date requestDate, Date responseDate, ProtocolVersion version, Header[] responseHeaders, byte[] responseBytes, int status, String reason){ + + super(); this.requestDate = requestDate; this.responseDate = responseDate; - version = response.getProtocolVersion(); - responseHeaders = response.getAllHeaders(); - StatusLine sl = response.getStatusLine(); - status = sl.getStatusCode(); - reason = sl.getReasonPhrase(); - + this.version = version; + this.responseHeaders.setHeaders(responseHeaders); + this.status = status; + this.reason = reason; body = responseBytes; } - public void setProtocolVersion(ProtocolVersion version) { - this.version = version; + /** + * Constructor used to create a copy of an existing entry, while adding another variant URI to it. + * @param toCopy CacheEntry to be duplicated + * @param variantURI URI to add + */ + private CacheEntry(CacheEntry toCopy, String variantURI){ + this(toCopy.getRequestDate(), + toCopy.getResponseDate(), + toCopy.getProtocolVersion(), + toCopy.getAllHeaders(), + toCopy.getBody(), + toCopy.getStatusCode(), + toCopy.getReasonPhrase()); + + variantURIs.addAll(toCopy.getVariantURIs()); + variantURIs.add(variantURI); } + public CacheEntry addVariantURI(String variantURI){ + return new CacheEntry(this,variantURI); + } + public ProtocolVersion getProtocolVersion() { return version; } @@ -115,63 +124,30 @@ return this.status; } - public void setRequestDate(Date requestDate) { - this.requestDate = requestDate; - } - public Date getRequestDate() { return requestDate; } - public void setResponseDate(Date responseDate) { - this.responseDate = responseDate; - } - public Date getResponseDate() { return this.responseDate; } - public void setBody(byte[] body) { - this.body = body; - } - public byte[] getBody() { return body; } public Header[] getAllHeaders() { - return responseHeaders; + return responseHeaders.getAllHeaders(); } - public void setResponseHeaders(Header[] responseHeaders) { - this.responseHeaders = responseHeaders; - } - public Header getFirstHeader(String name) { - for (Header h : responseHeaders) { - if (h.getName().equals(name)) - return h; + return responseHeaders.getFirstHeader(name); - } + } - return null; - } - public Header[] getHeaders(String name) { - - ArrayList
headers = new ArrayList
(); - - for (Header h : this.responseHeaders) { - if (h.getName().equals(name)) - headers.add(h); + return responseHeaders.getHeaders(name); - } + } - Header[] headerArray = new Header[headers.size()]; - - headers.toArray(headerArray); - - return headerArray; - } - /** * * @return Response Date header value @@ -377,12 +353,14 @@ out.defaultWriteObject(); // write (non-serializable) responseHeaders - if (null == responseHeaders || responseHeaders.length < 1) + if (null == responseHeaders || responseHeaders.getAllHeaders().length < 1) return; - String[][] sheaders = new String[responseHeaders.length][2]; - for (int i = 0; i < responseHeaders.length; i++) { - sheaders[i][0] = responseHeaders[i].getName(); - sheaders[i][1] = responseHeaders[i].getValue(); + int headerCount = responseHeaders.getAllHeaders().length; + Header[] headers = responseHeaders.getAllHeaders(); + String[][] sheaders = new String[headerCount][2]; + for (int i = 0; i < headerCount; i++) { + sheaders[i][0] = headers[i].getName(); + sheaders[i][1] = headers[i].getValue(); } out.writeObject(sheaders); @@ -402,14 +380,10 @@ String[] sheader = sheaders[i]; headers[i] = new BasicHeader(sheader[0], sheader[1]); } - this.responseHeaders = headers; + this.responseHeaders.setHeaders(headers); } - public void addVariantURI(String URI) { - this.variantURIs.add(URI); - } - public Set getVariantURIs() { return Collections.unmodifiableSet(this.variantURIs); } Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestSizeLimitedResponseReader.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestSizeLimitedResponseReader.java (revision 940065) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestSizeLimitedResponseReader.java (revision ) @@ -34,7 +34,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; -import org.apache.http.client.cache.impl.SizeLimitedResponseReader; import org.easymock.classextension.EasyMock; import org.junit.Assert; import org.junit.Before; Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryGenerator.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryGenerator.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryGenerator.java (revision ) @@ -26,13 +26,15 @@ */ package org.apache.http.client.cache.impl; -import java.io.IOException; -import java.util.Date; - import org.apache.http.HttpResponse; import org.apache.http.annotation.Immutable; +import java.io.IOException; +import java.util.Date; + /** + * Generates a {@link CacheEntry} from a {@link HttpResponse} + * * @since 4.1 */ @Immutable @@ -41,7 +43,15 @@ public CacheEntry generateEntry(Date requestDate, Date responseDate, HttpResponse response, byte[] responseBytes) throws IOException { - return new CacheEntry(requestDate, responseDate, response, responseBytes); + + return new CacheEntry(requestDate, + responseDate, + response.getProtocolVersion(), + response.getAllHeaders(), + responseBytes, + response.getStatusLine().getStatusCode(), + response.getStatusLine().getReasonPhrase()); + } } Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHeaderGroup.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHeaderGroup.java (revision ) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHeaderGroup.java (revision ) @@ -0,0 +1,11 @@ +package org.apache.http.client.cache.impl; + +import org.apache.http.message.HeaderGroup; + +import java.io.Serializable; + +/** + */ +public class CachedHeaderGroup extends HeaderGroup implements Serializable { + private static final long serialVersionUID = -4572663568087431896L; +} Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java (revision ) @@ -42,6 +42,8 @@ public static final String TRACE_METHOD = "TRACE"; public static final String LAST_MODIFIED = "Last-Modified"; + public static final String IF_MATCH = "If-Match"; + public static final String IF_RANGE = "If-Range"; public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; public static final String IF_NONE_MATCH = "If-None-Match"; Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryUpdater.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryUpdater.java (revision 939814) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryUpdater.java (revision ) @@ -26,14 +26,10 @@ */ package org.apache.http.client.cache.impl; -import java.util.Date; - import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.ProtocolVersion; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.CacheEntryUpdater; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; @@ -43,8 +39,17 @@ import org.junit.Before; import org.junit.Test; +import java.util.Date; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; + public class TestCacheEntryUpdater { + + private static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1); + + private HttpResponse mockResponse; private CacheEntry mockCacheEntry; private Date requestDate; @@ -81,92 +86,125 @@ } @Test - public void testUpdateCacheEntry() { - mockImplMethods("mergeHeaders"); - mockCacheEntry.setRequestDate(requestDate); - mockCacheEntry.setResponseDate(responseDate); - impl.mergeHeaders(mockCacheEntry, mockResponse); + public void testUpdateCacheEntryReturnsDifferentEntryInstance() { + CacheEntry entry = getEntry(new Header[]{}); + BasicHttpResponse response = new BasicHttpResponse(HTTP_1_1, 200, "OK"); + replayMocks(); - impl.updateCacheEntry(mockCacheEntry, requestDate, responseDate, mockResponse); + CacheEntry newEntry = impl.updateCacheEntry(entry, requestDate, responseDate, response); verifyMocks(); + + assertNotSame(newEntry, entry); + } @Test - public void testExistingHeadersNotInResponseDontChange() { + public void testHeadersAreMergedCorrectly() { - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.setResponseHeaders(new Header[] { + Header[] headers = { new BasicHeader("Date", DateUtils.formatDate(responseDate)), - new BasicHeader("ETag", "eTag") }); + new BasicHeader("ETag", "\"etag\"")}; + CacheEntry cacheEntry = getEntry(headers); + HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion( "http", 1, 1), HttpStatus.SC_NOT_MODIFIED, "")); - response.setHeaders(new Header[] {}); + response.setHeaders(new Header[]{}); - impl.mergeHeaders(cacheEntry, response); + CacheEntry updatedEntry = impl.updateCacheEntry(cacheEntry, new Date(), new Date(), response); - Assert.assertEquals(2, cacheEntry.getAllHeaders().length); + Assert.assertEquals(2, updatedEntry.getAllHeaders().length); - headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(responseDate)); - headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag"); + headersContain(updatedEntry.getAllHeaders(), "Date", DateUtils.formatDate(responseDate)); + headersContain(updatedEntry.getAllHeaders(), "ETag", "\"etag\""); } @Test public void testNewerHeadersReplaceExistingHeaders() { - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.setResponseHeaders(new Header[] { + + Header[] headers = { new BasicHeader("Date", DateUtils.formatDate(requestDate)), - new BasicHeader("Cache-Control", "private"), new BasicHeader("ETag", "eTag"), + new BasicHeader("Cache-Control", "private"), new BasicHeader("ETag", "\"etag\""), new BasicHeader("Last-Modified", DateUtils.formatDate(requestDate)), - new BasicHeader("Cache-Control", "max-age=0"), }); + new BasicHeader("Cache-Control", "max-age=0"),}; + CacheEntry cacheEntry = getEntry(headers); HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion( "http", 1, 1), HttpStatus.SC_NOT_MODIFIED, "")); - response.setHeaders(new Header[] { + response.setHeaders(new Header[]{ new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)), - new BasicHeader("Cache-Control", "public"), }); + new BasicHeader("Cache-Control", "public"),}); - impl.mergeHeaders(cacheEntry, response); + CacheEntry updatedEntry = impl.updateCacheEntry(cacheEntry, new Date(), new Date(), response); - Assert.assertEquals(4, cacheEntry.getAllHeaders().length); - headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate)); - headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag"); - headersContain(cacheEntry.getAllHeaders(), "Last-Modified", DateUtils + Assert.assertEquals(4, updatedEntry.getAllHeaders().length); + + headersContain(updatedEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate)); + headersContain(updatedEntry.getAllHeaders(), "ETag", "\"etag\""); + headersContain(updatedEntry.getAllHeaders(), "Last-Modified", DateUtils .formatDate(responseDate)); - headersContain(cacheEntry.getAllHeaders(), "Cache-Control", "public"); + headersContain(updatedEntry.getAllHeaders(), "Cache-Control", "public"); } @Test public void testNewHeadersAreAddedByMerge() { - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.setResponseHeaders(new Header[] { + Header[] headers = { new BasicHeader("Date", DateUtils.formatDate(requestDate)), - new BasicHeader("ETag", "eTag"), }); + new BasicHeader("ETag", "\"etag\"")}; + CacheEntry cacheEntry = getEntry(headers); HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion( "http", 1, 1), HttpStatus.SC_NOT_MODIFIED, "")); - response.setHeaders(new Header[] { + response.setHeaders(new Header[]{ new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)), - new BasicHeader("Cache-Control", "public"), }); + new BasicHeader("Cache-Control", "public"),}); - impl.mergeHeaders(cacheEntry, response); + CacheEntry updatedEntry = impl.updateCacheEntry(cacheEntry, new Date(), new Date(), response); - Assert.assertEquals(4, cacheEntry.getAllHeaders().length); - headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate)); - headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag"); - headersContain(cacheEntry.getAllHeaders(), "Last-Modified", DateUtils + Assert.assertEquals(4, updatedEntry.getAllHeaders().length); + + headersContain(updatedEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate)); + headersContain(updatedEntry.getAllHeaders(), "ETag", "\"etag\""); + headersContain(updatedEntry.getAllHeaders(), "Last-Modified", DateUtils .formatDate(responseDate)); - headersContain(cacheEntry.getAllHeaders(), "Cache-Control", "public"); + headersContain(updatedEntry.getAllHeaders(), "Cache-Control", "public"); } + @Test + public void testUpdatedEntryHasLatestRequestAndResponseDates() { + + Date now = new Date(); + + Date tenSecondsAgo = new Date(now.getTime() - 10000L); + Date eightSecondsAgo = new Date(now.getTime() - 8000L); + + Date twoSecondsAgo = new Date(now.getTime() - 2000L); + Date oneSecondAgo = new Date(now.getTime() - 1000L); + + Header[] headers = new Header[]{}; + + CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, HTTP_1_1, headers, new byte[]{}, 200, "OK"); + + HttpResponse response = new BasicHttpResponse(HTTP_1_1, 200, "OK"); + + CacheEntry updated = impl.updateCacheEntry(entry, twoSecondsAgo, oneSecondAgo, response); + + assertEquals(twoSecondsAgo, updated.getRequestDate()); + assertEquals(oneSecondAgo, updated.getResponseDate()); + + } + + + // UTILITY + private void headersContain(Header[] headers, String name, String value) { for (Header header : headers) { if (header.getName().equals(name)) { @@ -178,10 +216,12 @@ Assert.fail("Header [" + name + ": " + value + "] not found in headers."); } - private void mockImplMethods(String... methods) { - implMocked = true; - impl = EasyMock.createMockBuilder(CacheEntryUpdater.class).addMockedMethods(methods) - .createMock(); + + private CacheEntry getEntry(Header[] headers) { + return getEntry(new Date(), new Date(), headers); } + private CacheEntry getEntry(Date requestDate, Date responseDate, Header[] headers) { + return new CacheEntry(requestDate, responseDate, HTTP_1_1, headers, new byte[]{}, 200, "OK"); -} + } +} Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java (revision 940061) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java (revision ) @@ -26,27 +26,15 @@ */ package org.apache.http.client.cache.impl; -import java.io.IOException; -import java.net.URI; -import java.util.Date; -import java.util.List; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolException; -import org.apache.http.ProtocolVersion; -import org.apache.http.RequestLine; -import org.apache.http.StatusLine; +import org.apache.http.*; import org.apache.http.client.ClientProtocolException; 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.HttpCacheOperationException; import org.apache.http.client.cache.HttpCacheUpdateCallback; -import org.apache.http.client.cache.HttpCache; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.impl.client.DefaultHttpClient; @@ -55,12 +43,17 @@ import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; +import java.io.IOException; +import java.net.URI; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + /** * @since 4.1 */ public class CachingHttpClient implements HttpClient { - private static final Log LOG = LogFactory.getLog(CachingHttpClient.class); private final static int MAX_CACHE_ENTRIES = 1000; private final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192; @@ -82,12 +75,15 @@ private final int maxObjectSizeBytes; private final CacheEntryUpdater cacheEntryUpdater; - private volatile long cacheHits; - private volatile long cacheMisses; - private volatile long cacheUpdates; + private volatile AtomicLong cacheHits = new AtomicLong(); + private volatile AtomicLong cacheMisses = new AtomicLong(); + private volatile AtomicLong cacheUpdates = new AtomicLong(); + private final ResponseProtocolCompliance responseCompliance; private final RequestProtocolCompliance requestCompliance; + private final Log LOG = LogFactory.getLog(CachingHttpClient.class); + public CachingHttpClient() { this.backend = new DefaultHttpClient(); this.maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES; @@ -142,13 +138,13 @@ } public CachingHttpClient(HttpClient backend, ResponseCachingPolicy responseCachingPolicy, - CacheEntryGenerator cacheEntryGenerator, URIExtractor uriExtractor, - HttpCache responseCache, CachedHttpResponseGenerator responseGenerator, - CacheInvalidator cacheInvalidator, CacheableRequestPolicy cacheableRequestPolicy, - CachedResponseSuitabilityChecker suitabilityChecker, - ConditionalRequestBuilder conditionalRequestBuilder, CacheEntryUpdater entryUpdater, - ResponseProtocolCompliance responseCompliance, - RequestProtocolCompliance requestCompliance) { + CacheEntryGenerator cacheEntryGenerator, URIExtractor uriExtractor, + HttpCache responseCache, CachedHttpResponseGenerator responseGenerator, + CacheInvalidator cacheInvalidator, CacheableRequestPolicy cacheableRequestPolicy, + CachedResponseSuitabilityChecker suitabilityChecker, + ConditionalRequestBuilder conditionalRequestBuilder, CacheEntryUpdater entryUpdater, + ResponseProtocolCompliance responseCompliance, + RequestProtocolCompliance requestCompliance) { this.maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES; this.backend = backend; this.responseCachingPolicy = responseCachingPolicy; @@ -165,60 +161,153 @@ this.requestCompliance = requestCompliance; } + /** + * Return the number of times that the cache successfully answered an HttpRequest + * for a document of information from the server. + * + * @return long the number of cache successes + */ public long getCacheHits() { - return cacheHits; + return cacheHits.get(); } + /** + * Return the number of times that the cache was unable to answer an HttpRequest + * for a document of information from the server. + * + * @return long the number of cache failures/misses + */ public long getCacheMisses() { - return cacheMisses; + return cacheMisses.get(); } + /** + * Return the number of times that the cache was able to revalidate + * an existing cache entry for a document of information from the server. + * + * @return long the number of cache revalidations + */ public long getCacheUpdates() { - return cacheUpdates; + return cacheUpdates.get(); } + /** + * Execute an {@link HttpRequest} @ a given {@link HttpHost} + * + * @param target the target host for the request. + * Implementations may accept null + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @return HttpResponse The cached entry or the result of a backend call + * @throws IOException + */ public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException { HttpContext defaultContext = null; return execute(target, request, defaultContext); } + /** + * Execute an {@link HttpRequest} @ a given {@link HttpHost} with a specified + * {@link ResponseHandler} that will deal with the result of the call. + * + * @param target the target host for the request. + * Implementations may accept null + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * @param The Return Type Identified by the generic type of the {@link ResponseHandler} + * @return T The response type as handled by ResponseHandler + * @throws IOException + */ public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler) throws IOException { + ResponseHandler responseHandler) throws IOException { return execute(target, request, responseHandler, null); } + /** + * Execute an {@link HttpRequest} @ a given {@link HttpHost} with a specified + * {@link ResponseHandler} that will deal with the result of the call using + * a specific {@link HttpContext} + * + * @param target the target host for the request. + * Implementations may accept null + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * null to use the default context + * @param The Return Type Identified by the generic type of the {@link ResponseHandler} + * @return T The response type as handled by ResponseHandler + * @throws IOException + */ public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler, HttpContext context) throws IOException { + ResponseHandler responseHandler, HttpContext context) throws IOException { HttpResponse resp = execute(target, request, context); return responseHandler.handleResponse(resp); } + /** + * @param request the request to execute + * @return HttpResponse The cached entry or the result of a backend call + * @throws IOException + */ public HttpResponse execute(HttpUriRequest request) throws IOException { HttpContext context = null; return execute(request, context); } + /** + * @param request the request to execute + * @param context the context to use for the execution, or + * null to use the default context + * @return HttpResponse The cached entry or the result of a backend call + * @throws IOException + */ public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException { URI uri = request.getURI(); HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()); return execute(httpHost, request, context); } + /** + * @param request the request to execute + * @param responseHandler the response handler + * @param The Return Type Identified by the generic type of the {@link ResponseHandler} + * @return T The response type as handled by ResponseHandler + * @throws IOException + */ public T execute(HttpUriRequest request, ResponseHandler responseHandler) throws IOException { return execute(request, responseHandler, null); } + /** + * @param request the request to execute + * @param responseHandler the response handler + * @param context + * @param The Return Type Identified by the generic type of the {@link ResponseHandler} + * @return T The response type as handled by ResponseHandler + * @throws IOException + */ public T execute(HttpUriRequest request, ResponseHandler responseHandler, - HttpContext context) throws IOException { + HttpContext context) throws IOException { HttpResponse resp = execute(request, context); return responseHandler.handleResponse(resp); } + /** + * @return + */ public ClientConnectionManager getConnectionManager() { return backend.getConnectionManager(); } + /** + * @return + */ public HttpParams getParams() { return backend.getParams(); } @@ -233,7 +322,7 @@ try { entry = responseCache.getEntry(uri); } catch (HttpCacheOperationException probablyIgnore) { - // TODO: do something useful with this exception + LOG.warn("Cache: Was unable to get an entry from the cache based on the uri provided.", probablyIgnore); } if (entry == null || !entry.hasVariants()) @@ -247,6 +336,17 @@ } } + /** + * @param target the target host for the request. + * Implementations may accept null + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, or + * null to use the default context + * @return + * @throws IOException + */ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException { @@ -254,8 +354,7 @@ return new OptionsHttp11Response(); } - List fatalError = requestCompliance - .requestIsFatallyNonCompliant(request); + List fatalError = requestCompliance.requestIsFatallyNonCompliant(request); for (RequestProtocolError error : fatalError) { return requestCompliance.getErrorForRequest(error); @@ -275,13 +374,13 @@ CacheEntry entry = getCacheEntry(target, request); if (entry == null) { - cacheMisses++; + cacheMisses.getAndIncrement(); LOG.debug("CLIENT: Cache Miss."); return callBackend(target, request, context); } LOG.debug("CLIENT: Cache HIT."); - cacheHits++; + cacheHits.getAndIncrement(); if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry)) { return responseGenerator.generateResponse(entry); @@ -294,8 +393,8 @@ return revalidateCacheEntry(target, request, context, entry); } catch (IOException ioex) { HttpResponse response = responseGenerator.generateResponse(entry); - response.addHeader(HeaderConstants.WARNING, "111 Revalidation Failed - " - + ioex.getMessage()); + response.addHeader(HeaderConstants.WARNING, "111 Revalidation Failed - " + ioex.getMessage()); + LOG.debug("111 revalidation failed due to exception: " + ioex); return response; } catch (ProtocolException e) { throw new ClientProtocolException(e); @@ -328,7 +427,7 @@ LOG.debug("CLIENT: Calling the backend."); HttpResponse backendResponse = backend.execute(target, request, context); return handleBackendResponse(target, request, requestDate, getCurrentDate(), - backendResponse); + backendResponse); } catch (ClientProtocolException cpex) { throw cpex; } catch (IOException ex) { @@ -340,9 +439,8 @@ } protected HttpResponse revalidateCacheEntry(HttpHost target, HttpRequest request, - HttpContext context, CacheEntry cacheEntry) throws IOException, ProtocolException { + HttpContext context, CacheEntry cacheEntry) throws IOException, ProtocolException { - HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, - cacheEntry); + HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry); Date requestDate = getCurrentDate(); HttpResponse backendResponse = backend.execute(target, conditionalRequest, context); @@ -351,15 +449,14 @@ int statusCode = backendResponse.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) { - cacheUpdates++; - cacheEntryUpdater.updateCacheEntry(cacheEntry, requestDate, responseDate, - backendResponse); - storeInCache(target, request, cacheEntry); - return responseGenerator.generateResponse(cacheEntry); + cacheUpdates.getAndIncrement(); + CacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(cacheEntry, requestDate, responseDate, backendResponse); + storeInCache(target, request, updatedEntry); + return responseGenerator.generateResponse(updatedEntry); } return handleBackendResponse(target, conditionalRequest, requestDate, responseDate, - backendResponse); + backendResponse); } protected void storeInCache(HttpHost target, HttpRequest request, CacheEntry entry) { @@ -369,7 +466,7 @@ HttpCacheUpdateCallback callback = storeVariantEntry(target, request, entry); responseCache.updateCacheEntry(uri, callback); } catch (HttpCacheOperationException probablyIgnore) { - // TODO: do something useful with this exception + LOG.warn("Cache: Was unable to PUT/UPDATE an entry into the cache based on the uri provided.", probablyIgnore); } } else { storeNonVariantEntry(target, request, entry); @@ -381,31 +478,35 @@ try { responseCache.putEntry(uri, entry); } catch (HttpCacheOperationException probablyIgnore) { - // TODO: do something useful with this exception + LOG.warn("Cache: Was unable to PUT an entry into the cache based on the uri provided.", probablyIgnore); } } protected HttpCacheUpdateCallback storeVariantEntry(final HttpHost target, final HttpRequest req, - final CacheEntry entry) { + final CacheEntry entry) { + return new HttpCacheUpdateCallback() { public CacheEntry getUpdatedEntry(CacheEntry existing) throws HttpCacheOperationException { + return doGetUpdatedParentEntry(existing, target, req, entry); + } + }; + } + + protected CacheEntry doGetUpdatedParentEntry(CacheEntry existing, HttpHost target, HttpRequest req, CacheEntry entry) throws HttpCacheOperationException { + - String variantURI = uriExtractor.getVariantURI(target, req, entry); - responseCache.putEntry(variantURI, entry); + String variantURI = uriExtractor.getVariantURI(target, req, entry); + responseCache.putEntry(variantURI, entry); - if (existing != null) { + if (existing != null) { - existing.addVariantURI(variantURI); - return existing; + return existing.addVariantURI(variantURI); - } else { + } else { - entry.addVariantURI(variantURI); - return entry; + return entry.addVariantURI(variantURI); - } - } + } + } - }; - } protected HttpResponse handleBackendResponse(HttpHost target, HttpRequest request, - Date requestDate, Date responseDate, HttpResponse backendResponse) throws IOException { + Date requestDate, Date responseDate, HttpResponse backendResponse) throws IOException { LOG.debug("CLIENT: Handling Backend response."); responseCompliance.ensureProtocolCompliance(request, backendResponse); @@ -421,7 +522,7 @@ } CacheEntry entry = cacheEntryGenerator.generateEntry(requestDate, responseDate, - backendResponse, responseReader.getResponseBytes()); + backendResponse, responseReader.getResponseBytes()); storeInCache(target, request, entry); return responseGenerator.generateResponse(entry); } @@ -430,6 +531,7 @@ try { responseCache.removeEntry(uri); } catch (HttpCacheOperationException coe) { + LOG.warn("Cache: Was unable to remove an entry from the cache based on the uri provided.", coe); // TODO: track failed state } return backendResponse; Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java (revision 939814) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java (revision ) @@ -26,21 +26,19 @@ */ package org.apache.http.client.cache.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.Arrays; -import java.util.Date; - import org.apache.http.Header; import org.apache.http.HttpVersion; import org.apache.http.ProtocolVersion; import org.apache.http.client.cache.HttpCacheEntrySerializer; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.DefaultCacheEntrySerializer; import org.apache.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.Date; + public class TestDefaultCacheEntrySerializer { @Test @@ -64,7 +62,6 @@ private CacheEntry newCacheEntry() { - CacheEntry cacheEntry = new CacheEntry(); Header[] headers = new Header[5]; for (int i = 0; i < headers.length; i++) { @@ -73,12 +70,8 @@ ProtocolVersion version = new HttpVersion(1, 1); String body = "Lorem ipsum dolor sit amet"; - cacheEntry.setResponseHeaders(headers); - cacheEntry.setProtocolVersion(version); - cacheEntry.setRequestDate(new Date()); - cacheEntry.setResponseDate(new Date()); - cacheEntry.setBody(body.getBytes()); + CacheEntry cacheEntry = new CacheEntry(new Date(),new Date(), version, headers, body.getBytes(),200,"OK"); - + return cacheEntry; } Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java (revision ) @@ -46,8 +46,12 @@ public class ResponseCachingPolicy { private final int maxObjectSizeBytes; - private static final Log LOG = LogFactory.getLog(ResponseCachingPolicy.class); + private final Log LOG = LogFactory.getLog(ResponseCachingPolicy.class); + /** + * + * @param maxObjectSizeBytes + */ public ResponseCachingPolicy(int maxObjectSizeBytes) { this.maxObjectSizeBytes = maxObjectSizeBytes; } Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java (revision ) @@ -38,16 +38,23 @@ @Immutable public class ConditionalRequestBuilder { + /** + * + * @param request + * @param cacheEntry + * @return + * @throws ProtocolException + */ public HttpRequest buildConditionalRequest(HttpRequest request, CacheEntry cacheEntry) throws ProtocolException { RequestWrapper wrapperRequest = new RequestWrapper(request); wrapperRequest.resetHeaders(); Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG); if (eTag != null) { - wrapperRequest.setHeader("If-None-Match", eTag.getValue()); + wrapperRequest.setHeader(HeaderConstants.IF_NONE_MATCH, eTag.getValue()); } else { Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED); - wrapperRequest.setHeader("If-Modified-Since", lastModified.getValue()); + wrapperRequest.setHeader(HeaderConstants.IF_MODIFIED_SINCE, lastModified.getValue()); } return wrapperRequest; Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java (revision 939814) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java (revision ) @@ -26,34 +26,40 @@ */ package org.apache.http.client.cache.impl; -import java.util.Date; -import java.util.Set; - import org.apache.http.Header; -import org.apache.http.client.cache.impl.CacheEntry; +import org.apache.http.ProtocolVersion; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; +import java.util.Date; +import java.util.Set; + +import static junit.framework.Assert.assertFalse; + public class TestCacheEntry { + private static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP",1,1); + @Test public void testGetHeadersReturnsCorrectHeaders() { Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"), new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(2, entry.getHeaders("bar").length); } + private CacheEntry getEntry(Header[] headers) { + return getEntry(new Date(), new Date(), headers); + } + @Test public void testGetFirstHeaderReturnsCorrectHeader() { Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"), new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") }; + CacheEntry entry = getEntry(headers); - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); Assert.assertEquals("barValue1", entry.getFirstHeader("bar").getValue()); } @@ -62,9 +68,9 @@ Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"), new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); + Assert.assertEquals(0, entry.getHeaders("baz").length); } @@ -72,9 +78,8 @@ public void testGetFirstHeaderReturnsNullIfNoneMatch() { Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"), new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") }; + CacheEntry entry = getEntry(headers); - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); Assert.assertEquals(null, entry.getFirstHeader("quux")); } @@ -82,8 +87,7 @@ @Test public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() { Header[] headers = new Header[0]; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(2147483648L, entry.getApparentAgeSecs()); } @@ -96,13 +100,17 @@ Header[] headers = new Header[] { new BasicHeader("Date", DateUtils .formatDate(tenSecondsAgo)) }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); - entry.setResponseDate(sixSecondsAgo); + + CacheEntry entry = getEntry(now, sixSecondsAgo, headers); + Assert.assertEquals(4, entry.getApparentAgeSecs()); } + private CacheEntry getEntry(Date requestDate, Date responseDate, Header[] headers) { + return new CacheEntry(requestDate,responseDate,HTTP_1_1,headers,new byte[]{},200,"OK"); + } + @Test public void testNegativeApparentAgeIsBroughtUpToZero() { Date now = new Date(); @@ -112,17 +120,14 @@ Header[] headers = new Header[] { new BasicHeader("Date", DateUtils .formatDate(sixSecondsAgo)) }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); - entry.setResponseDate(tenSecondsAgo); - + CacheEntry entry = getEntry(now,tenSecondsAgo,headers); Assert.assertEquals(0, entry.getApparentAgeSecs()); } @Test public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() { Header[] headers = new Header[] { new BasicHeader("Age", "10"), }; - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,headers, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -130,15 +135,15 @@ return 6; } }; - entry.setResponseHeaders(headers); + Assert.assertEquals(10, entry.getCorrectedReceivedAgeSecs()); } @Test public void testCorrectedReceivedAgeIsApparentAgeIfLarger() { Header[] headers = new Header[] { new BasicHeader("Age", "6"), }; - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,headers, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -146,7 +151,6 @@ return 10; } }; - entry.setResponseHeaders(headers); Assert.assertEquals(10, entry.getCorrectedReceivedAgeSecs()); } @@ -157,16 +161,17 @@ Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); - CacheEntry entry = new CacheEntry(); - entry.setRequestDate(tenSecondsAgo); - entry.setResponseDate(sixSecondsAgo); + Header[] headers = new Header[]{}; + CacheEntry entry = new CacheEntry(tenSecondsAgo,sixSecondsAgo,new ProtocolVersion("HTTP",1,1),headers,new byte[]{},200,"OK"); + + Assert.assertEquals(4, entry.getResponseDelaySecs()); } @Test public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() { - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -187,7 +192,7 @@ final Date now = new Date(); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),sixSecondsAgo,HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -195,14 +200,13 @@ return now; } }; - entry.setResponseDate(sixSecondsAgo); Assert.assertEquals(6, entry.getResidentTimeSecs()); } @Test public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() { - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -221,16 +225,14 @@ @Test public void testFreshnessLifetimeIsSMaxAgeIfPresent() { Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); } @Test public void testFreshnessLifetimeIsMaxAgeIfPresent() { Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); } @@ -238,14 +240,12 @@ public void testFreshnessLifetimeIsMostRestrictiveOfMaxAgeAndSMaxAge() { Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"), new BasicHeader("Cache-Control", "s-maxage=20") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"), new BasicHeader("Cache-Control", "s-maxage=10") }; - entry = new CacheEntry(); - entry.setResponseHeaders(headers); + entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); } @@ -258,8 +258,7 @@ new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); } @@ -272,8 +271,7 @@ new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(10, entry.getFreshnessLifetimeSecs()); } @@ -286,14 +284,13 @@ new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertEquals(4, entry.getFreshnessLifetimeSecs()); } @Test public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() { - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -312,7 +309,7 @@ @Test public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() { - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -331,7 +328,7 @@ @Test public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() { - CacheEntry entry = new CacheEntry() { + CacheEntry entry = new CacheEntry(new Date(),new Date(),HTTP_1_1,new Header[]{}, new byte[]{},200,"OK") { private static final long serialVersionUID = 1L; @Override @@ -350,46 +347,53 @@ @Test public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() { - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(new Header[] { + + Header[] headers = { new BasicHeader("Expires", DateUtils.formatDate(new Date())), - new BasicHeader("ETag", "somevalue") }); + new BasicHeader("ETag", "somevalue")}; + CacheEntry entry = getEntry(headers); + Assert.assertTrue(entry.isRevalidatable()); } @Test public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() { - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(new Header[] { + Header[] headers = { new BasicHeader("Expires", DateUtils.formatDate(new Date())), - new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) }); + new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) }; + CacheEntry entry = getEntry(headers); Assert.assertTrue(entry.isRevalidatable()); } @Test public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() { - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(new Header[] { + + Header[] headers = { new BasicHeader("Expires", DateUtils.formatDate(new Date())), - new BasicHeader("Cache-Control", "public") }); + new BasicHeader("Cache-Control", "public") }; + + CacheEntry entry = getEntry(headers); + + assertFalse(entry.isRevalidatable()); } + + @Test public void testCacheEntryWithNoVaryHeaderDoesNotHaveVariants() { Header[] headers = new Header[0]; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + + CacheEntry entry = getEntry(headers); Assert.assertFalse(entry.hasVariants()); } @Test public void testCacheEntryWithOneVaryHeaderHasVariants() { Header[] headers = { new BasicHeader("Vary", "User-Agent") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertTrue(entry.hasVariants()); } @@ -397,20 +401,27 @@ public void testCacheEntryWithMultipleVaryHeadersHasVariants() { Header[] headers = { new BasicHeader("Vary", "User-Agent"), new BasicHeader("Vary", "Accept-Encoding") }; - CacheEntry entry = new CacheEntry(); - entry.setResponseHeaders(headers); + CacheEntry entry = getEntry(headers); Assert.assertTrue(entry.hasVariants()); } @Test + public void testCacheEntryWithVaryStarHasVariants(){ + Header[] headers = { new BasicHeader("Vary", "*") }; + CacheEntry entry = getEntry(headers); + Assert.assertTrue(entry.hasVariants()); + } + + @Test public void testCacheEntryCanStoreMultipleVariantUris() { - CacheEntry entry = new CacheEntry(); + Header[] headers = new Header[]{}; + CacheEntry entry = getEntry(headers); - entry.addVariantURI("foo"); - entry.addVariantURI("bar"); + CacheEntry addedOne = entry.addVariantURI("foo"); + CacheEntry addedTwo = addedOne.addVariantURI("bar"); - Set variants = entry.getVariantURIs(); + Set variants = addedTwo.getVariantURIs(); Assert.assertTrue(variants.contains("foo")); Assert.assertTrue(variants.contains("bar")); @@ -419,76 +430,67 @@ @Test public void testMalformedDateHeaderIsIgnored() { - Header[] h = new Header[] { new BasicHeader("Date", "asdf") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Date", "asdf") }; + CacheEntry entry = getEntry(headers); - Date d = e.getDateValue(); + Date d = entry.getDateValue(); Assert.assertNull(d); - } @Test public void testMalformedContentLengthReturnsNegativeOne() { - Header[] h = new Header[] { new BasicHeader("Content-Length", "asdf") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Content-Length", "asdf") }; + CacheEntry entry = getEntry(headers); - long length = e.getContentLengthValue(); + long length = entry.getContentLengthValue(); Assert.assertEquals(-1, length); - } @Test public void testNegativeAgeHeaderValueReturnsMaxAge() { - Header[] h = new Header[] { new BasicHeader("Age", "-100") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Age", "-100") }; + CacheEntry entry = getEntry(headers); - long length = e.getAgeValue(); + long length = entry.getAgeValue(); Assert.assertEquals(CacheEntry.MAX_AGE, length); - } @Test public void testMalformedAgeHeaderValueReturnsMaxAge() { - Header[] h = new Header[] { new BasicHeader("Age", "asdf") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Age", "asdf") }; + CacheEntry entry = getEntry(headers); - long length = e.getAgeValue(); + long length = entry.getAgeValue(); Assert.assertEquals(CacheEntry.MAX_AGE, length); - } @Test public void testMalformedCacheControlMaxAgeHeaderReturnsZero() { - Header[] h = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") }; + CacheEntry entry = getEntry(headers); - long maxage = e.getMaxAge(); + long maxage = entry.getMaxAge(); Assert.assertEquals(0, maxage); - } @Test public void testMalformedExpirationDateReturnsNull() { - Header[] h = new Header[] { new BasicHeader("Expires", "asdf") }; - CacheEntry e = new CacheEntry(); - e.setResponseHeaders(h); + Header[] headers = new Header[] { new BasicHeader("Expires", "asdf") }; + CacheEntry entry = getEntry(headers); - Date expirationDate = e.getExpirationDate(); + Date expirationDate = entry.getExpirationDate(); Assert.assertNull(expirationDate); } + + } Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java (revision 940065) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java (revision ) @@ -26,41 +26,11 @@ */ package org.apache.http.client.cache.impl; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolException; -import org.apache.http.RequestLine; -import org.apache.http.StatusLine; +import org.apache.http.*; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; -import org.apache.http.client.cache.HttpCacheUpdateCallback; import org.apache.http.client.cache.HttpCache; -import org.apache.http.client.cache.impl.BasicHttpCache; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.CacheEntryGenerator; -import org.apache.http.client.cache.impl.CacheEntryUpdater; -import org.apache.http.client.cache.impl.CacheInvalidator; -import org.apache.http.client.cache.impl.CacheableRequestPolicy; -import org.apache.http.client.cache.impl.CachedHttpResponseGenerator; -import org.apache.http.client.cache.impl.CachedResponseSuitabilityChecker; -import org.apache.http.client.cache.impl.CachingHttpClient; -import org.apache.http.client.cache.impl.ConditionalRequestBuilder; -import org.apache.http.client.cache.impl.RequestProtocolCompliance; -import org.apache.http.client.cache.impl.RequestProtocolError; -import org.apache.http.client.cache.impl.ResponseCachingPolicy; -import org.apache.http.client.cache.impl.ResponseProtocolCompliance; -import org.apache.http.client.cache.impl.SizeLimitedResponseReader; -import org.apache.http.client.cache.impl.URIExtractor; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; @@ -78,8 +48,19 @@ import org.junit.Ignore; import org.junit.Test; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static junit.framework.Assert.assertTrue; + public class TestCachingHttpClient { + private static 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"; @@ -106,6 +87,7 @@ private HttpResponse mockBackendResponse; private CacheEntry mockCacheEntry; private CacheEntry mockVariantCacheEntry; + private CacheEntry mockUpdatedCacheEntry; private URIExtractor mockExtractor; private CacheEntryGenerator mockEntryGenerator; private CachedHttpResponseGenerator mockResponseGenerator; @@ -153,6 +135,7 @@ mockBackendResponse = EasyMock.createMock(HttpResponse.class); mockUriRequest = EasyMock.createMock(HttpUriRequest.class); mockCacheEntry = EasyMock.createMock(CacheEntry.class); + mockUpdatedCacheEntry = EasyMock.createMock(CacheEntry.class); mockVariantCacheEntry = EasyMock.createMock(CacheEntry.class); mockExtractor = EasyMock.createMock(URIExtractor.class); mockEntryGenerator = EasyMock.createMock(CacheEntryGenerator.class); @@ -168,14 +151,15 @@ mockRequestProtocolCompliance = EasyMock.createMock(RequestProtocolCompliance.class); mockRequestLine = EasyMock.createMock(RequestLine.class); + requestDate = new Date(System.currentTimeMillis() - 1000); responseDate = new Date(); host = new HttpHost("foo.example.com"); impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance); + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance); } private void replayMocks() { @@ -264,7 +248,7 @@ replayMocks(); HttpResponse result = impl.handleBackendResponse(host, mockRequest, requestDate, - responseDate, mockBackendResponse); + responseDate, mockBackendResponse); verifyMocks(); Assert.assertSame(mockCachedResponse, result); @@ -317,26 +301,25 @@ } @Test - public void testCacheUpdateCallbackCreatesNewParentEntryWhenParentEntryNull() throws Exception { + public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception { final String variantURI = "variantURI"; - extractVariantURI(variantURI); - putInCache(variantURI); + final CacheEntry entry = new CacheEntry(new Date(), new Date(),HTTP_1_1,new Header[]{},new byte[]{},200,"OK"); - variantURIAddedToCacheEntry(variantURI); + extractVariantURI(variantURI, entry); + putInCache(variantURI, entry); replayMocks(); - HttpCacheUpdateCallback callbackImpl = impl - .storeVariantEntry(host, mockRequest, mockCacheEntry); - callbackImpl.getUpdatedEntry(null); + + CacheEntry updatedEntry = impl.doGetUpdatedParentEntry(null, host, mockRequest, entry); + verifyMocks(); - } - private void variantURIAddedToCacheEntry(String variantURI) { - mockCacheEntry.addVariantURI(variantURI); + assertTrue(updatedEntry.getVariantURIs().contains(variantURI)); } + @Test public void testCacheMissCausesBackendRequest() throws Exception { mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND); @@ -417,15 +400,15 @@ getCurrentDateReturns(responseDate); backendResponseCodeIs(HttpStatus.SC_OK); cacheEntryUpdaterCalled(); - cacheEntryHasVariants(false); + cacheEntryHasVariants(false, mockUpdatedCacheEntry); extractTheURI("http://foo.example.com"); - putInCache("http://foo.example.com"); - responseIsGeneratedFromCache(); + putInCache("http://foo.example.com", mockUpdatedCacheEntry); + responseIsGeneratedFromCache(mockUpdatedCacheEntry); replayMocks(); HttpResponse result = impl.revalidateCacheEntry(host, mockRequest, mockContext, - mockCacheEntry); + mockCacheEntry); verifyMocks(); @@ -446,14 +429,13 @@ backendResponseCodeIs(HttpStatus.SC_NOT_MODIFIED); cacheEntryUpdaterCalled(); - storeInCacheWasCalled(); + storeInCacheWasCalled(mockUpdatedCacheEntry); - responseIsGeneratedFromCache(); + responseIsGeneratedFromCache(mockUpdatedCacheEntry); replayMocks(); - HttpResponse result = impl.revalidateCacheEntry(host, mockRequest, mockContext, - mockCacheEntry); + HttpResponse result = impl.revalidateCacheEntry(host, mockRequest, mockContext, mockCacheEntry); verifyMocks(); @@ -509,7 +491,7 @@ replayMocks(); HttpResponse result = impl.handleBackendResponse(host, mockRequest, currentDate, - currentDate, mockBackendResponse); + currentDate, mockBackendResponse); verifyMocks(); Assert.assertSame(mockBackendResponse, result); @@ -628,10 +610,10 @@ final HttpRequest theRequest = mockRequest; final HttpResponse theResponse = mockBackendResponse; impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) { Assert.assertSame(theHost, target); @@ -660,13 +642,13 @@ final ResponseHandler theHandler = mockHandler; final Object value = new Object(); impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public T execute(HttpHost target, HttpRequest request, - ResponseHandler rh, HttpContext context) { + ResponseHandler rh, HttpContext context) { Assert.assertSame(theHost, target); Assert.assertSame(theRequest, request); Assert.assertSame(theHandler, rh); @@ -700,10 +682,10 @@ final HttpResponse theResponse = mockBackendResponse; final HttpContext theContext = mockContext; impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) { Assert.assertSame(theHost, target); @@ -732,10 +714,10 @@ final HttpUriRequest theRequest = mockUriRequest; final HttpResponse theResponse = mockBackendResponse; impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public HttpResponse execute(HttpUriRequest request, HttpContext context) { Assert.assertSame(theRequest, request); @@ -762,10 +744,10 @@ final HttpContext theContext = mockContext; final HttpResponse theResponse = mockBackendResponse; impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public HttpResponse execute(HttpHost hh, HttpRequest req, HttpContext ctx) { Assert.assertEquals("sch", hh.getSchemeName()); @@ -795,13 +777,13 @@ final HttpResponse theResponse = mockBackendResponse; final Object theValue = new Object(); impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public T execute(HttpUriRequest request, ResponseHandler handler, - HttpContext context) throws IOException { + HttpContext context) throws IOException { Assert.assertSame(theRequest, request); Assert.assertNull(context); c.incr(); @@ -830,10 +812,10 @@ final HttpResponse theResponse = mockBackendResponse; final Object theValue = new Object(); impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator, - mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, - mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, - mockCacheEntryUpdater, mockResponseProtocolCompliance, - mockRequestProtocolCompliance) { + mockExtractor, mockCache, mockResponseGenerator, mockInvalidator, + mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder, + mockCacheEntryUpdater, mockResponseProtocolCompliance, + mockRequestProtocolCompliance) { @Override public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException { @@ -949,7 +931,7 @@ response.getEntity().writeTo(s1); return s1.toByteArray(); } catch (Exception ex) { - return new byte[] {}; + return new byte[]{}; } @@ -976,8 +958,9 @@ } private void cacheEntryUpdaterCalled() { + EasyMock.expect( - mockCacheEntryUpdater.updateCacheEntry(mockCacheEntry, requestDate, responseDate, + mockCacheEntryUpdater.updateCacheEntry(mockCacheEntry, requestDate, responseDate, - mockBackendResponse); + mockBackendResponse)).andReturn(mockUpdatedCacheEntry); } private void getCacheEntryReturns(CacheEntry entry) { @@ -1061,43 +1044,64 @@ org.easymock.EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(b); } + private void cacheEntryHasVariants(boolean b, CacheEntry entry) { + EasyMock.expect(entry.hasVariants()).andReturn(b); + } + private void responseIsGeneratedFromCache() { org.easymock.EasyMock.expect(mockResponseGenerator.generateResponse(mockCacheEntry)) .andReturn(mockCachedResponse); } + private void responseIsGeneratedFromCache(CacheEntry entry) { + org.easymock.EasyMock.expect(mockResponseGenerator.generateResponse(entry)) + .andReturn(mockCachedResponse); + } + private void extractTheURI(String theURI) { org.easymock.EasyMock.expect(mockExtractor.getURI(host, mockRequest)).andReturn(theURI); } private void extractVariantURI(String variantURI) { + extractVariantURI(variantURI,mockCacheEntry); + } + + private void extractVariantURI(String variantURI, CacheEntry entry){ org.easymock.EasyMock - .expect(mockExtractor.getVariantURI(host, mockRequest, mockCacheEntry)).andReturn( + .expect(mockExtractor.getVariantURI(host, mockRequest, entry)).andReturn( - variantURI); + variantURI); } private void putInCache(String theURI) throws Exception { mockCache.putEntry(theURI, mockCacheEntry); } + private void putInCache(String theURI, CacheEntry entry) throws Exception { + mockCache.putEntry(theURI, entry); + } + private void generateCacheEntry(Date requestDate, Date responseDate, byte[] bytes) throws IOException { org.easymock.EasyMock.expect( mockEntryGenerator.generateEntry(requestDate, responseDate, mockBackendResponse, - bytes)).andReturn(mockCacheEntry); + bytes)).andReturn(mockCacheEntry); } private void handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response) throws IOException { org.easymock.EasyMock.expect( impl.handleBackendResponse(host, request, requestDate, responseDate, - mockBackendResponse)).andReturn(response); + mockBackendResponse)).andReturn(response); } private void storeInCacheWasCalled() { impl.storeInCache(host, mockRequest, mockCacheEntry); } + private void storeInCacheWasCalled(CacheEntry entry) { + impl.storeInCache(host, mockRequest, entry); + } + private void responseProtocolValidationIsCalled() throws ClientProtocolException { mockResponseProtocolCompliance.ensureProtocolCompliance(mockRequest, mockBackendResponse); } @@ -1117,10 +1121,10 @@ private void mockImplMethods(String... methods) { mockedImpl = true; impl = EasyMock.createMockBuilder(CachingHttpClient.class).withConstructor(mockBackend, - mockResponsePolicy, mockEntryGenerator, mockExtractor, mockCache, - mockResponseGenerator, mockInvalidator, mockRequestPolicy, mockSuitabilityChecker, - mockConditionalRequestBuilder, mockCacheEntryUpdater, - mockResponseProtocolCompliance, mockRequestProtocolCompliance).addMockedMethods( + mockResponsePolicy, mockEntryGenerator, mockExtractor, mockCache, + mockResponseGenerator, mockInvalidator, mockRequestPolicy, mockSuitabilityChecker, + mockConditionalRequestBuilder, mockCacheEntryUpdater, + mockResponseProtocolCompliance, mockRequestProtocolCompliance).addMockedMethods( methods).createMock(); } } Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java (revision ) @@ -46,6 +46,12 @@ @Immutable public class DefaultCacheEntrySerializer implements HttpCacheEntrySerializer { + /** + * + * @param cacheEntry + * @param os + * @throws IOException + */ public void writeTo(CacheEntry cacheEntry, OutputStream os) throws IOException { ObjectOutputStream oos = null; @@ -77,6 +83,12 @@ } + /** + * + * @param is + * @return + * @throws IOException + */ public CacheEntry readFrom(InputStream is) throws IOException { ObjectInputStream ois = null; @@ -112,4 +124,4 @@ } -} \ No newline at end of file +} Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheableRequestPolicy.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheableRequestPolicy.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheableRequestPolicy.java (revision ) @@ -42,7 +42,7 @@ @Immutable public class CacheableRequestPolicy { - private static final Log LOG = LogFactory.getLog(CacheableRequestPolicy.class); + private final Log LOG = LogFactory.getLog(CacheableRequestPolicy.class); /** * Determines if an HttpRequest can be served from the cache. Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheInvalidator.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheInvalidator.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheInvalidator.java (revision ) @@ -48,19 +48,30 @@ private final HttpCache cache; private final URIExtractor uriExtractor; - private static final Log LOG = LogFactory.getLog(CacheInvalidator.class); + private final Log LOG = LogFactory.getLog(CacheInvalidator.class); + /** + * + * @param uriExtractor + * @param cache + */ public CacheInvalidator(URIExtractor uriExtractor, HttpCache cache) { this.uriExtractor = uriExtractor; this.cache = cache; } + /** + * Remove cache entries from the cache that are no longer fresh or + * have been invalidated in some way. + * + * @param host The backend host we are talking to + * @param req The HttpRequest to that host + */ public void flushInvalidatedCacheEntries(HttpHost host, HttpRequest req) { LOG.debug("CacheInvalidator: flushInvalidatedCacheEntries, BEGIN"); if (requestShouldNotBeCached(req)) { - LOG - .debug("CacheInvalidator: flushInvalidatedCacheEntries, Request should not be cached"); + LOG.debug("CacheInvalidator: flushInvalidatedCacheEntries, Request should not be cached"); try { String theUri = uriExtractor.getURI(host, req); @@ -76,6 +87,7 @@ cache.removeEntry(theUri); } } catch (HttpCacheOperationException coe) { + LOG.warn("Cache: Was unable to REMOVE an entry from the cache based on the uri provided.", coe); // TODO: track failed state } } @@ -124,4 +136,4 @@ return false; } -} \ No newline at end of file +} Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java (revision 939883) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java (revision ) @@ -35,14 +35,15 @@ import org.apache.http.annotation.Immutable; /** - * Determines whether a given response can be cached. + * Determines whether a given {@link CacheEntry} is suitable to be + * used as a response for a given {@link HttpRequest}. * * @since 4.1 */ @Immutable public class CachedResponseSuitabilityChecker { - private static final Log LOG = LogFactory.getLog(CachedResponseSuitabilityChecker.class); + private final Log LOG = LogFactory.getLog(CachedResponseSuitabilityChecker.class); /** * @param host @@ -60,28 +61,24 @@ } if (!entry.contentLengthHeaderMatchesActualLength()) { - LOG - .debug("CachedResponseSuitabilityChecker: Cache Entry Content Length and header information DO NOT match."); + LOG.debug("CachedResponseSuitabilityChecker: Cache Entry Content Length and header information DO NOT match."); return false; } if (entry.modifiedSince(request)) { - LOG - .debug("CachedResponseSuitabilityChecker: Cache Entry modified times didn't line up. Cache Entry should NOT be used."); + LOG.debug("CachedResponseSuitabilityChecker: Cache Entry modified times didn't line up. Cache Entry should NOT be used."); return false; } for (Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { for (HeaderElement elt : ccHdr.getElements()) { if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { - LOG - .debug("CachedResponseSuitabilityChecker: Response contained NO CACHE directive, cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response contained NO CACHE directive, cache was NOT suitable."); return false; } if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) { - LOG - .debug("CachedResponseSuitabilityChecker: Response contained NO SORE directive, cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response contained NO SORE directive, cache was NOT suitable."); return false; } @@ -89,14 +86,12 @@ try { int maxage = Integer.parseInt(elt.getValue()); if (entry.getCurrentAgeSecs() > maxage) { - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable due to max age."); return false; } } catch (NumberFormatException nfe) { // err conservatively - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable. "+nfe); return false; } } @@ -105,14 +100,12 @@ try { int maxstale = Integer.parseInt(elt.getValue()); if (entry.getFreshnessLifetimeSecs() > maxstale) { - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable due to Max stale freshness"); return false; } } catch (NumberFormatException nfe) { // err conservatively - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable. "+nfe); return false; } } @@ -121,14 +114,12 @@ try { int minfresh = Integer.parseInt(elt.getValue()); if (entry.getFreshnessLifetimeSecs() < minfresh) { - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable due to min fresh freshness requirement"); return false; } } catch (NumberFormatException nfe) { // err conservatively - LOG - .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable."); + LOG.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable. " + nfe); return false; } } Index: httpclient-cache/src/test/Resources/log4j.properties =================================================================== --- httpclient-cache/src/test/Resources/log4j.properties (revision ) +++ httpclient-cache/src/test/Resources/log4j.properties (revision ) @@ -0,0 +1,9 @@ +#set the level of the root logger to DEBUG and set its appender as an appender named X +log4j.rootLogger = DEBUG + +#set the appender named X to be a console appender +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender + +#set the layout for the appender X +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.conversionPattern=%m%n \ No newline at end of file Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedHttpResponseGenerator.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedHttpResponseGenerator.java (revision 940058) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedHttpResponseGenerator.java (revision ) @@ -26,31 +26,24 @@ */ package org.apache.http.client.cache.impl; -import java.util.Date; - import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.CachedHttpResponseGenerator; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHeader; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class TestCachedHttpResponseGenerator { @Test public void testResponseHasContentLength() { - CacheEntry entry = new CacheEntry(); Header[] hdrs = new Header[] {}; byte[] buf = new byte[] { 1, 2, 3, 4, 5 }; - entry.setResponseHeaders(hdrs); - entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1)); - entry.setBody(buf); - entry.setResponseDate(new Date()); - entry.setRequestDate(new Date()); + CacheEntry entry = new CacheEntry(new Date(),new Date(),new ProtocolVersion("HTTP", 1, 1),hdrs,buf,200,"OK"); CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -64,15 +57,12 @@ @Test public void testContentLengthIsNotAddedWhenTransferEncodingIsPresent() { - CacheEntry entry = new CacheEntry(); + Header[] hdrs = new Header[] { new BasicHeader("Transfer-Encoding", "chunked") }; byte[] buf = new byte[] { 1, 2, 3, 4, 5 }; - entry.setResponseHeaders(hdrs); - entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1)); - entry.setBody(buf); - entry.setResponseDate(new Date()); - entry.setRequestDate(new Date()); + CacheEntry entry = new CacheEntry(new Date(),new Date(),new ProtocolVersion("HTTP", 1, 1),hdrs,buf,200,"OK"); + CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -83,8 +73,7 @@ @Test public void testResponseMatchesCacheEntry() { - CacheEntry entry = new CacheEntry(); - buildEntry(entry); + CacheEntry entry = buildEntry(); CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -98,8 +87,7 @@ @Test public void testResponseStatusCodeMatchesCacheEntry() { - CacheEntry entry = new CacheEntry(); - buildEntry(entry); + CacheEntry entry = buildEntry(); CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -110,17 +98,9 @@ @Test public void testAgeHeaderIsPopulatedWithCurrentAgeOfCacheEntryIfNonZero() { final long currAge = 10L; - CacheEntry entry = new CacheEntry() { - private static final long serialVersionUID = 1L; - @Override - public long getCurrentAgeSecs() { - return currAge; - } - }; + CacheEntry entry = buildEntryWithCurrentAge(currAge); - buildEntry(entry); - CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -132,15 +112,8 @@ @Test public void testAgeHeaderIsNotPopulatedIfCurrentAgeOfCacheEntryIsZero() { final long currAge = 0L; - CacheEntry entry = new CacheEntry() { - private static final long serialVersionUID = 1L; - @Override - public long getCurrentAgeSecs() { - return currAge; - } - }; - buildEntry(entry); + CacheEntry entry = buildEntryWithCurrentAge(currAge); CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -151,16 +124,8 @@ @Test public void testAgeHeaderIsPopulatedWithMaxAgeIfCurrentAgeTooBig() { - final long currAge = CacheEntry.MAX_AGE + 1L; - CacheEntry entry = new CacheEntry() { - private static final long serialVersionUID = 1L; - @Override - public long getCurrentAgeSecs() { - return currAge; - } - }; - buildEntry(entry); + CacheEntry entry = buildEntryWithCurrentAge(CacheEntry.MAX_AGE + 1L); CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator(); HttpResponse response = gen.generateResponse(entry); @@ -170,7 +135,7 @@ Assert.assertEquals(CacheEntry.MAX_AGE, Long.parseLong(ageHdr.getValue())); } - private CacheEntry buildEntry(CacheEntry entry) { + private CacheEntry buildEntry() { Date now = new Date(); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); @@ -179,14 +144,30 @@ Header[] hdrs = { new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo)), new BasicHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)), new BasicHeader("Content-Length", "150") }; - entry.setRequestDate(tenSecondsAgo); - entry.setResponseDate(sixSecondsAgo); - entry.setBody(new byte[] {}); - entry.setResponseHeaders(hdrs); - entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1)); + return new CacheEntry(tenSecondsAgo,sixSecondsAgo,new ProtocolVersion("HTTP", 1, 1),hdrs,new byte[]{},200,"OK"); + } - return entry; + private CacheEntry buildEntryWithCurrentAge(final long currAge){ + Date now = new Date(); + Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); + Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); + Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); + Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L); + Header[] hdrs = { new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo)), + new BasicHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)), + new BasicHeader("Content-Length", "150") }; + + + return new CacheEntry(tenSecondsAgo,sixSecondsAgo,new ProtocolVersion("HTTP", 1, 1),hdrs,new byte[]{},200,"OK"){ + private static final long serialVersionUID = 1L; + + @Override + public long getCurrentAgeSecs() { + return currAge; - } + } + + }; -} + } +} Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCache.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCache.java (revision 939814) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCache.java (revision ) @@ -26,10 +26,7 @@ */ package org.apache.http.client.cache.impl; -import org.apache.http.client.cache.HttpCacheOperationException; -import org.apache.http.client.cache.HttpCacheUpdateCallback; -import org.apache.http.client.cache.impl.BasicHttpCache; -import org.apache.http.client.cache.impl.CacheEntry; +import org.easymock.classextension.EasyMock; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -37,26 +34,26 @@ public class TestResponseCache { private BasicHttpCache cache; + private CacheEntry mockEntry; @Before public void setUp() { cache = new BasicHttpCache(5); + mockEntry = EasyMock.createMock(CacheEntry.class); } @Test public void testEntryRemainsInCacheWhenPutThere() { - CacheEntry entry = new CacheEntry(); - cache.putEntry("foo", entry); + cache.putEntry("foo", mockEntry); CacheEntry cachedEntry = cache.getEntry("foo"); - Assert.assertSame(entry, cachedEntry); + Assert.assertSame(mockEntry, cachedEntry); } @Test public void testRemovedEntriesDoNotExistAnymore() { - CacheEntry entry = new CacheEntry(); - cache.putEntry("foo", entry); + cache.putEntry("foo", mockEntry); cache.removeEntry("foo"); @@ -69,13 +66,13 @@ public void testCacheHoldsNoMoreThanSpecifiedMaxEntries() { BasicHttpCache cache = new BasicHttpCache(1); - CacheEntry entry1 = new CacheEntry(); + CacheEntry entry1 = EasyMock.createMock(CacheEntry.class); cache.putEntry("foo", entry1); - CacheEntry entry2 = new CacheEntry(); + CacheEntry entry2 = EasyMock.createMock(CacheEntry.class); cache.putEntry("bar", entry2); - CacheEntry entry3 = new CacheEntry(); + CacheEntry entry3 = EasyMock.createMock(CacheEntry.class); cache.putEntry("baz", entry3); CacheEntry e1 = cache.getEntry("foo"); @@ -96,7 +93,7 @@ // fill the cache with entries for (int i = 0; i < max_size; i++) { - CacheEntry entry = new CacheEntry(); + CacheEntry entry = EasyMock.createMock(CacheEntry.class); cache.putEntry("entry" + i, entry); } @@ -105,7 +102,7 @@ // add another entry, which kicks out the eldest (should be the 2nd one // created), and becomes the new MRU entry - CacheEntry newMru = new CacheEntry(); + CacheEntry newMru = EasyMock.createMock(CacheEntry.class); cache.putEntry("newMru", newMru); // get the original second eldest @@ -123,7 +120,7 @@ public void testZeroMaxSizeCacheDoesNotStoreAnything() { BasicHttpCache cache = new BasicHttpCache(0); - CacheEntry entry = new CacheEntry(); + CacheEntry entry = EasyMock.createMock(CacheEntry.class); cache.putEntry("foo", entry); CacheEntry gone = cache.getEntry("foo"); @@ -131,33 +128,33 @@ Assert.assertNull("This cache should not have anything in it!", gone); } - @Test - public void testCacheEntryCallbackUpdatesCacheEntry() throws HttpCacheOperationException { +// @Test +// public void testCacheEntryCallbackUpdatesCacheEntry() throws HttpCacheOperationException { +// +// final byte[] expectedArray = new byte[] { 1, 2, 3, 4, 5 }; +// +// CacheEntry entry = EasyMock.createMock(CacheEntry.class); +// CacheEntry entry2 = EasyMock.createMock(CacheEntry.class); +// +// cache.putEntry("foo", entry); +// cache.putEntry("bar", entry2); +// +// cache.updateCacheEntry("foo", new HttpCacheUpdateCallback() { +// +// public CacheEntry getUpdatedEntry(CacheEntry existing) { +// existing.setBody(expectedArray); +// +// cache.removeEntry("bar"); +// return existing; +// } +// }); +// +// CacheEntry afterUpdate = cache.getEntry("foo"); +// CacheEntry bar = cache.getEntry("bar"); +// +// Assert.assertNull(bar); +// +// Assert.assertArrayEquals(expectedArray, afterUpdate.getBody()); +// } - final byte[] expectedArray = new byte[] { 1, 2, 3, 4, 5 }; - - CacheEntry entry = new CacheEntry(); - CacheEntry entry2 = new CacheEntry(); - - cache.putEntry("foo", entry); - cache.putEntry("bar", entry2); - - cache.updateCacheEntry("foo", new HttpCacheUpdateCallback() { - - public CacheEntry getUpdatedEntry(CacheEntry existing) { - existing.setBody(expectedArray); - - cache.removeEntry("bar"); - return existing; - } +} - }); - - CacheEntry afterUpdate = cache.getEntry("foo"); - CacheEntry bar = cache.getEntry("bar"); - - Assert.assertNull(bar); - - Assert.assertArrayEquals(expectedArray, afterUpdate.getBody()); - } - -} Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java (revision 940063) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java (revision ) @@ -26,40 +26,29 @@ */ package org.apache.http.client.cache.impl; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; -import java.util.Random; - -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolVersion; +import org.apache.http.*; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.cache.HttpCache; -import org.apache.http.client.cache.impl.BasicHttpCache; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.CachingHttpClient; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.RequestWrapper; import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.message.BasicHttpEntityEnclosingRequest; -import org.apache.http.message.BasicHttpRequest; -import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.*; import org.apache.http.protocol.HttpContext; +import org.apache.log4j.BasicConfigurator; import org.easymock.Capture; import org.easymock.IExpectationSetters; import org.easymock.classextension.EasyMock; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Random; + /** * We are a conditionally-compliant HTTP/1.1 client with a cache. However, a lot * of the rules for proxies apply to us, as far as proper operation of the @@ -87,6 +76,12 @@ private CachingHttpClient impl; + + @BeforeClass + public static void configureLogging(){ + BasicConfigurator.configure(); + } + @SuppressWarnings("unchecked") @Before public void setUp() { @@ -2063,52 +2058,53 @@ public void testCacheEntryIsUpdatedWithNewFieldValuesIn304Response() throws Exception { Date now = new Date(); - Date inOneSecond = new Date(now.getTime() + 1000L); - HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1); - HttpResponse resp1 = make200Response(); - resp1.setHeader("Cache-Control", "max-age=3600"); - resp1.setHeader("ETag", "\"etag\""); + Date inFiveSeconds = new Date(now.getTime() + 5000L); - HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1); - req2.setHeader("Cache-Control", "max-age=0,max-stale=0"); + HttpRequest initialRequest = new BasicHttpRequest("GET", "/", HTTP_1_1); - HttpRequest conditionalValidation = new BasicHttpRequest("GET", "/", HTTP_1_1); - conditionalValidation.setHeader("If-None-Match", "\"etag\""); + HttpResponse cachedResponse = make200Response(); + cachedResponse.setHeader("Cache-Control", "max-age=3600"); + cachedResponse.setHeader("ETag", "\"etag\""); - HttpRequest unconditionalValidation = new BasicHttpRequest("GET", "/", HTTP_1_1); + HttpRequest secondRequest = new BasicHttpRequest("GET", "/", HTTP_1_1); + secondRequest.setHeader("Cache-Control", "max-age=0,max-stale=0"); + HttpRequest conditionalValidationRequest = new BasicHttpRequest("GET", "/", HTTP_1_1); + conditionalValidationRequest.setHeader("If-None-Match", "\"etag\""); + + HttpRequest unconditionalValidationRequest = new BasicHttpRequest("GET", "/", HTTP_1_1); + // to be used if the cache generates a conditional validation - HttpResponse resp2 = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, - "Not Modified"); - resp2.setHeader("Date", DateUtils.formatDate(inOneSecond)); - resp2.setHeader("Server", "MockUtils/1.0"); - resp2.setHeader("ETag", "\"etag\""); - resp2.setHeader("X-Extra", "junk"); + HttpResponse conditionalResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified"); + conditionalResponse.setHeader("Date", DateUtils.formatDate(inFiveSeconds)); + conditionalResponse.setHeader("Server", "MockUtils/1.0"); + conditionalResponse.setHeader("ETag", "\"etag\""); + conditionalResponse.setHeader("X-Extra", "junk"); // to be used if the cache generates an unconditional validation - HttpResponse resp3 = make200Response(); - resp3.setHeader("Date", DateUtils.formatDate(inOneSecond)); - resp3.setHeader("ETag", "\"etag\""); + HttpResponse unconditionalResponse = make200Response(); + unconditionalResponse.setHeader("Date", DateUtils.formatDate(inFiveSeconds)); + unconditionalResponse.setHeader("ETag", "\"etag\""); Capture cap1 = new Capture(); Capture cap2 = new Capture(); EasyMock.expect( mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class), - (HttpContext) EasyMock.isNull())).andReturn(resp1); + (HttpContext) EasyMock.isNull())).andReturn(cachedResponse); EasyMock.expect( mockBackend.execute(EasyMock.eq(host), EasyMock.and( - eqRequest(conditionalValidation), EasyMock.capture(cap1)), - (HttpContext) EasyMock.isNull())).andReturn(resp2).times(0, 1); + eqRequest(conditionalValidationRequest), EasyMock.capture(cap1)), + (HttpContext) EasyMock.isNull())).andReturn(conditionalResponse).times(0, 1); EasyMock.expect( mockBackend.execute(EasyMock.eq(host), EasyMock.and( - eqRequest(unconditionalValidation), EasyMock.capture(cap2)), - (HttpContext) EasyMock.isNull())).andReturn(resp3).times(0, 1); + eqRequest(unconditionalValidationRequest), EasyMock.capture(cap2)), + (HttpContext) EasyMock.isNull())).andReturn(unconditionalResponse).times(0, 1); replayMocks(); - impl.execute(host, req1); - HttpResponse result = impl.execute(host, req2); + impl.execute(host, initialRequest); + HttpResponse result = impl.execute(host, secondRequest); verifyMocks(); @@ -2116,7 +2112,7 @@ || (!cap1.hasCaptured() && cap2.hasCaptured())); if (cap1.hasCaptured()) { - Assert.assertEquals(DateUtils.formatDate(inOneSecond), result.getFirstHeader("Date") + Assert.assertEquals(DateUtils.formatDate(inFiveSeconds), result.getFirstHeader("Date") .getValue()); Assert.assertEquals("junk", result.getFirstHeader("X-Extra").getValue()); } @@ -2297,17 +2293,20 @@ Date nineSecondsAgo = new Date(now.getTime() - 9 * 1000L); Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); - originResponse.setHeader("Date", DateUtils.formatDate(nineSecondsAgo)); - originResponse.setHeader("Cache-Control", "max-age=0"); - originResponse.setHeader("ETag", "\"etag\""); - originResponse.setHeader("Content-Length", "128"); + FakeHeaderGroup headerGroup = new FakeHeaderGroup(); + headerGroup.addHeader("Date", DateUtils.formatDate(nineSecondsAgo)); + headerGroup.addHeader("Cache-Control", "max-age=0"); + headerGroup.addHeader("ETag", "\"etag\""); + headerGroup.addHeader("Content-Length", "128"); + + byte[] bytes = new byte[128]; (new Random()).nextBytes(bytes); - CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, originResponse, bytes); + CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, HTTP_1_1, headerGroup.getAllHeaders(),bytes,200,"OK"); - mockCache.putEntry("http://foo.example.com/thing", entry); + mockCache.putEntry(EasyMock.eq("http://foo.example.com/thing"), EasyMock.isA(CacheEntry.class)); impl = new CachingHttpClient(mockBackend, mockCache, MAX_BYTES); @@ -2339,15 +2338,19 @@ Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); Date nineSecondsAgo = new Date(now.getTime() - 9 * 1000L); Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); + FakeHeaderGroup headerGroup = new FakeHeaderGroup(); - originResponse.setHeader("Date", DateUtils.formatDate(nineSecondsAgo)); - originResponse.setHeader("Cache-Control", "max-age=3600"); - originResponse.setHeader("Content-Length", "128"); + + headerGroup.addHeader("Date", DateUtils.formatDate(nineSecondsAgo)); + headerGroup.addHeader("Cache-Control", "max-age=3600"); + headerGroup.addHeader("Content-Length", "128"); byte[] bytes = new byte[128]; (new Random()).nextBytes(bytes); - CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, originResponse, bytes); + CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, HTTP_1_1, headerGroup.getAllHeaders(),bytes,200,"OK"); + + impl = new CachingHttpClient(mockBackend, mockCache, MAX_BYTES); EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); @@ -2377,15 +2380,19 @@ Date nineSecondsAgo = new Date(now.getTime() - 9 * 1000L); Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); - originResponse.setHeader("Date", DateUtils.formatDate(nineSecondsAgo)); - originResponse.setHeader("Cache-Control", "max-age=0"); - originResponse.setHeader("Content-Length", "128"); - originResponse.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo)); + FakeHeaderGroup headerGroup = new FakeHeaderGroup(); + + headerGroup.addHeader("Date", DateUtils.formatDate(nineSecondsAgo)); + headerGroup.addHeader("Cache-Control", "max-age=0"); + headerGroup.addHeader("Content-Length", "128"); + headerGroup.addHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo)); byte[] bytes = new byte[128]; (new Random()).nextBytes(bytes); - CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, originResponse, bytes); + CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, HTTP_1_1, headerGroup.getAllHeaders(),bytes,200,"OK"); + + impl = new CachingHttpClient(mockBackend, mockCache, MAX_BYTES); EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); @@ -2558,14 +2565,16 @@ Date nineSecondsAgo = new Date(now.getTime() - 9 * 1000L); Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); - originResponse.setHeader("Date", DateUtils.formatDate(nineSecondsAgo)); - originResponse.setHeader("Cache-Control", "max-age=3600"); - originResponse.setHeader("Content-Length", "128"); + + FakeHeaderGroup headerGroup = new FakeHeaderGroup(); + + headerGroup.setHeader("Date", DateUtils.formatDate(nineSecondsAgo)); + headerGroup.setHeader("Cache-Control", "max-age=3600"); + headerGroup.setHeader("Content-Length", "128"); byte[] bytes = new byte[128]; (new Random()).nextBytes(bytes); - originResponse.setEntity(new ByteArrayEntity(bytes)); - CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, originResponse, bytes); + CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, HTTP_1_1, headerGroup.getAllHeaders(),bytes,200,"OK"); impl = new CachingHttpClient(mockBackend, mockCache, MAX_BYTES); @@ -2599,16 +2608,18 @@ Date requestTime = new Date(thirtySixHoursAgo.getTime() - 1000L); Date responseTime = new Date(thirtySixHoursAgo.getTime() + 1000L); - originResponse.setHeader("Date", DateUtils.formatDate(thirtySixHoursAgo)); - originResponse.setHeader("Cache-Control", "public"); - originResponse.setHeader("Last-Modified", DateUtils.formatDate(oneYearAgo)); - originResponse.setHeader("Content-Length", "128"); + FakeHeaderGroup headerGroup = new FakeHeaderGroup(); + + headerGroup.setHeader("Date", DateUtils.formatDate(thirtySixHoursAgo)); + headerGroup.setHeader("Cache-Control", "public"); + headerGroup.setHeader("Last-Modified", DateUtils.formatDate(oneYearAgo)); + headerGroup.setHeader("Content-Length", "128"); byte[] bytes = new byte[128]; (new Random()).nextBytes(bytes); - originResponse.setEntity(new ByteArrayEntity(bytes)); - CacheEntry entry = new CacheEntry(requestTime, responseTime, originResponse, bytes); + CacheEntry entry = new CacheEntry(requestTime, responseTime, HTTP_1_1, headerGroup.getAllHeaders(),bytes,200,"OK"); + impl = new CachingHttpClient(mockBackend, mockCache, MAX_BYTES); HttpResponse validated = make200Response(); @@ -3139,4 +3150,16 @@ testDoesNotAddHeaderOnCacheHit("Last-Modified"); } + + + private class FakeHeaderGroup extends HeaderGroup{ + + public void addHeader(String name, String value){ + this.addHeader(new BasicHeader(name,value)); -} \ No newline at end of file + } + + public void setHeader(String name, String value){ + addHeader(name,value); + } + } +} \ No newline at end of file Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java (revision 940061) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java (revision ) @@ -26,17 +26,17 @@ */ package org.apache.http.client.cache.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.entity.InputStreamEntity; import org.apache.http.message.BasicHttpResponse; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + /** * @since 4.1 */ @@ -52,12 +52,17 @@ private byte[] sizeLimitedContent; private boolean outputStreamConsumed; + /** + * + * @param maxResponseSizeBytes + * @param response + */ public SizeLimitedResponseReader(int maxResponseSizeBytes, HttpResponse response) { this.maxResponseSizeBytes = maxResponseSizeBytes; this.response = response; } - public boolean isResponseTooLarge() throws IOException { + protected boolean isResponseTooLarge() throws IOException { if (!responseIsConsumed) isTooLarge = consumeResponse(); @@ -108,14 +113,14 @@ outputStreamConsumed = true; } - public byte[] getResponseBytes() { + protected byte[] getResponseBytes() { if (!outputStreamConsumed) consumeOutputStream(); return sizeLimitedContent; } - public HttpResponse getReconstructedResponse() { + protected HttpResponse getReconstructedResponse() { InputStream combinedStream = getCombinedInputStream(); Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java (revision 939814) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java (revision ) @@ -26,13 +26,10 @@ */ package org.apache.http.client.cache.impl; -import java.util.Date; - import org.apache.http.Header; import org.apache.http.HttpRequest; import org.apache.http.ProtocolException; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.ConditionalRequestBuilder; +import org.apache.http.ProtocolVersion; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpRequest; @@ -40,6 +37,8 @@ import org.junit.Before; import org.junit.Test; +import java.util.Date; + public class TestConditionalRequestBuilder { private ConditionalRequestBuilder impl; @@ -58,11 +57,11 @@ HttpRequest request = new BasicHttpRequest(theMethod, theUri); request.addHeader("Accept-Encoding", "gzip"); - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.setResponseHeaders(new Header[] { + Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatDate(new Date())), - new BasicHeader("Last-Modified", lastModified) }); + new BasicHeader("Last-Modified", lastModified) }; + CacheEntry cacheEntry = new CacheEntry(new Date(),new Date(),new ProtocolVersion("HTTP",1,1),headers, new byte[]{},200,"OK"); HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry); Assert.assertNotSame(request, newRequest); @@ -89,12 +88,14 @@ HttpRequest request = new BasicHttpRequest(theMethod, theUri); request.addHeader("Accept-Encoding", "gzip"); - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.setResponseHeaders(new Header[] { + Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatDate(new Date())), new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())), - new BasicHeader("ETag", theETag) }); + new BasicHeader("ETag", theETag) }; + CacheEntry cacheEntry = new CacheEntry(new Date(),new Date(),new ProtocolVersion("HTTP",1,1),headers, new byte[]{},200,"OK"); + + HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry); Assert.assertNotSame(request, newRequest); Index: httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java =================================================================== --- httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java (revision 939845) +++ httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java (revision ) @@ -40,6 +40,8 @@ private final InputStream inputStream2; /** + * Take two inputstreams and produce an object that makes them appear as if they + * are actually a 'single' input stream. * * @param inputStream1 * First stream to read Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheInvalidator.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheInvalidator.java (revision 940060) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheInvalidator.java (revision ) @@ -26,23 +26,16 @@ */ package org.apache.http.client.cache.impl; -import java.util.HashSet; -import java.util.Set; - -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.RequestLine; -import org.apache.http.client.cache.HttpCacheOperationException; +import org.apache.http.*; import org.apache.http.client.cache.HttpCache; -import org.apache.http.client.cache.impl.CacheEntry; -import org.apache.http.client.cache.impl.CacheInvalidator; -import org.apache.http.client.cache.impl.URIExtractor; +import org.apache.http.client.cache.HttpCacheOperationException; import org.easymock.classextension.EasyMock; import org.junit.Before; import org.junit.Test; +import java.util.HashSet; +import java.util.Set; + public class TestCacheInvalidator { private CacheInvalidator impl;