Index: httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheHttpCache.java
===================================================================
--- httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheHttpCache.java (revision 987194)
+++ httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheHttpCache.java (working copy)
@@ -27,12 +27,16 @@
package org.apache.http.impl.client.cache.ehcache;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import junit.framework.TestCase;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
+import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.impl.client.cache.CacheEntry;
import org.easymock.EasyMock;
import org.junit.Test;
@@ -41,38 +45,67 @@
private Ehcache mockCache;
private EhcacheHttpCacheStorage impl;
+ private HttpCacheEntrySerializer mockSerializer;
public void setUp() {
mockCache = EasyMock.createMock(Ehcache.class);
- impl = new EhcacheHttpCacheStorage(mockCache);
+ mockSerializer = EasyMock.createMock(HttpCacheEntrySerializer.class);
+ impl = new EhcacheHttpCacheStorage(mockCache, mockSerializer);
}
-
+
+ private void replayMocks(){
+ EasyMock.replay(mockCache);
+ EasyMock.replay(mockSerializer);
+ }
+
+ private void verifyMocks(){
+ EasyMock.verify(mockCache);
+ EasyMock.verify(mockSerializer);
+ }
+
@Test
public void testCachePut() throws IOException {
final String key = "foo";
final HttpCacheEntry value = new CacheEntry();
-
- Element e = new Element(key, value);
-
+
+ Element e = new Element(key, new byte[]{});
+
+ mockSerializer.writeTo(EasyMock.same(value), EasyMock.isA(OutputStream.class));
mockCache.put(e);
-
- EasyMock.replay(mockCache);
+
+ replayMocks();
impl.putEntry(key, value);
- EasyMock.verify(mockCache);
+ verifyMocks();
}
@Test
- public void testCacheGet() {
+ public void testCacheGetNullEntry() throws IOException {
+ final String key = "foo";
+
+ EasyMock.expect(mockCache.get(key)).andReturn(null);
+
+ replayMocks();
+ HttpCacheEntry resultingEntry = impl.getEntry(key);
+ verifyMocks();
+
+ assertNull(resultingEntry);
+ }
+
+ @Test
+ public void testCacheGet() throws IOException {
final String key = "foo";
final HttpCacheEntry cachedValue = new CacheEntry();
- Element element = new Element(key, cachedValue);
+
+ Element element = new Element(key, new byte[]{});
EasyMock.expect(mockCache.get(key))
.andReturn(element);
-
- EasyMock.replay(mockCache);
+ EasyMock.expect(mockSerializer.readFrom(EasyMock.isA(InputStream.class)))
+ .andReturn(cachedValue);
+
+ replayMocks();
HttpCacheEntry resultingEntry = impl.getEntry(key);
- EasyMock.verify(mockCache);
+ verifyMocks();
assertSame(cachedValue, resultingEntry);
}
@@ -83,9 +116,62 @@
EasyMock.expect(mockCache.remove(key)).andReturn(true);
- EasyMock.replay(mockCache);
+ replayMocks();
impl.removeEntry(key);
- EasyMock.verify(mockCache);
+ verifyMocks();
}
-
+
+ @Test
+ public void testCacheUpdateNullEntry() throws IOException {
+ final String key = "foo";
+ final HttpCacheEntry updatedValue = new CacheEntry();
+
+ Element element = new Element(key, new byte[]{});
+
+ HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
+ public HttpCacheEntry update(HttpCacheEntry old){
+ assertNull(old);
+ return updatedValue;
+ }
+ };
+
+ // get empty old entry
+ EasyMock.expect(mockCache.get(key)).andReturn(null);
+
+ // put new entry
+ mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock.isA(OutputStream.class));
+ mockCache.put(element);
+
+ replayMocks();
+ impl.updateEntry(key, callback);
+ verifyMocks();
+ }
+
+ @Test
+ public void testCacheUpdate() throws IOException {
+ final String key = "foo";
+ final HttpCacheEntry existingValue = new CacheEntry();
+ final HttpCacheEntry updatedValue = new CacheEntry();
+
+ Element existingElement = new Element(key, new byte[]{});
+
+ HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
+ public HttpCacheEntry update(HttpCacheEntry old){
+ assertEquals(existingValue, old);
+ return updatedValue;
+ }
+ };
+
+ // get existing old entry
+ EasyMock.expect(mockCache.get(key)).andReturn(existingElement);
+ EasyMock.expect(mockSerializer.readFrom(EasyMock.isA(InputStream.class))).andReturn(existingValue);
+
+ // update
+ mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock.isA(OutputStream.class));
+ EasyMock.expect(mockCache.replace(EasyMock.same(existingElement), EasyMock.isA(Element.class))).andReturn(true);
+
+ replayMocks();
+ impl.updateEntry(key, callback);
+ verifyMocks();
+ }
}
Index: httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java
===================================================================
--- httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java (revision 0)
+++ httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java (revision 0)
@@ -0,0 +1,104 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.http.impl.client.cache.ehcache;
+
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.Configuration;
+import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.cache.HttpCache;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.impl.client.cache.BasicHttpCache;
+import org.apache.http.impl.client.cache.CacheConfig;
+import org.apache.http.impl.client.cache.CachingHttpClient;
+import org.apache.http.impl.client.cache.HeapResourceFactory;
+import org.apache.http.impl.client.cache.HttpTestUtils;
+import org.apache.http.impl.client.cache.TestProtocolRequirements;
+import org.apache.http.message.BasicHttpRequest;
+import org.easymock.classextension.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+public class TestEhcacheProtocolRequirements extends TestProtocolRequirements{
+
+ private final String TEST_EHCACHE_NAME = "TestEhcacheProtocolRequirements-cache";
+
+ private static CacheManager CACHE_MANAGER;
+
+ @BeforeClass
+ public static void setUpGlobal() {
+ Configuration config = new Configuration();
+ config.addDefaultCache(
+ new CacheConfiguration("default", Integer.MAX_VALUE)
+ .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
+ .overflowToDisk(false));
+ CACHE_MANAGER = CacheManager.create(config);
+ }
+
+ @Override
+ @Before
+ public void setUp() {
+ host = new HttpHost("foo.example.com");
+
+ body = HttpTestUtils.makeBody(entityLength);
+
+ request = new BasicHttpRequest("GET", "/foo", HttpVersion.HTTP_1_1);
+
+ originResponse = make200Response();
+
+ params = new CacheConfig();
+ params.setMaxObjectSizeBytes(MAX_BYTES);
+
+ if (CACHE_MANAGER.cacheExists(TEST_EHCACHE_NAME)){
+ CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
+ }
+ CACHE_MANAGER.addCache(TEST_EHCACHE_NAME);
+ HttpCacheStorage storage = new EhcacheHttpCacheStorage(CACHE_MANAGER.getCache(TEST_EHCACHE_NAME));
+ cache = new BasicHttpCache(new HeapResourceFactory(), storage, params);
+ mockBackend = EasyMock.createMock(HttpClient.class);
+ mockCache = EasyMock.createMock(HttpCache.class);
+
+ impl = new CachingHttpClient(mockBackend, cache, params);
+ }
+
+ @After
+ public void tearDown(){
+ CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
+ }
+
+ @AfterClass
+ public static void tearDownGlobal(){
+ CACHE_MANAGER.shutdown();
+ }
+
+}
Index: httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
===================================================================
--- httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java (revision 0)
+++ httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java (revision 0)
@@ -0,0 +1,116 @@
+package org.apache.http.impl.client.cache;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.Header;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
+import org.apache.http.client.cache.Resource;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicStatusLine;
+
+public class TestHttpCacheEntrySerializers extends TestCase {
+
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ public void testDefaultSerializer() throws Exception {
+ readWriteVerify(new DefaultHttpCacheEntrySerializer());
+ }
+
+ public void readWriteVerify(HttpCacheEntrySerializer serializer) throws IOException {
+ // write the entry
+ HttpCacheEntry writeEntry = newCacheEntry();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ serializer.writeTo(writeEntry, out);
+
+ // read the entry
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ HttpCacheEntry readEntry = serializer.readFrom(in);
+
+ // compare
+ assertTrue(areEqual(readEntry, writeEntry));
+ }
+
+ private HttpCacheEntry newCacheEntry() throws UnsupportedEncodingException {
+ Header[] headers = new Header[5];
+ for (int i = 0; i < headers.length; i++) {
+ headers[i] = new BasicHeader("header" + i, "value" + i);
+ }
+ String body = "Lorem ipsum dolor sit amet";
+
+ ProtocolVersion pvObj = new ProtocolVersion("HTTP", 1, 1);
+ StatusLine slObj = new BasicStatusLine(pvObj, 200, "ok");
+ Set variants = new HashSet();
+ variants.add("test variant 1");
+ variants.add("test variant 2");
+
+ HttpCacheEntry cacheEntry = new HttpCacheEntry(new Date(), new Date(),
+ slObj, headers, new HeapResource(Base64.decodeBase64(body
+ .getBytes(UTF8.name()))), variants);
+
+ return cacheEntry;
+ }
+
+ private boolean areEqual(HttpCacheEntry one, HttpCacheEntry two) throws IOException {
+ // dates are only stored with second precision, so scrub milliseconds
+ if (!((one.getRequestDate().getTime() / 1000) == (two.getRequestDate()
+ .getTime() / 1000)))
+ return false;
+ if (!((one.getResponseDate().getTime() / 1000) == (two
+ .getResponseDate().getTime() / 1000)))
+ return false;
+ if (!one.getProtocolVersion().equals(two.getProtocolVersion()))
+ return false;
+
+ byte[] onesByteArray = resourceToBytes(one.getResource());
+ byte[] twosByteArray = resourceToBytes(two.getResource());
+
+ if (!Arrays.equals(onesByteArray,twosByteArray))
+ return false;
+
+ Header[] oneHeaders = one.getAllHeaders();
+ Header[] twoHeaders = one.getAllHeaders();
+ if (!(oneHeaders.length == twoHeaders.length))
+ return false;
+ for (int i = 0; i < oneHeaders.length; i++) {
+ if (!oneHeaders[i].getName().equals(twoHeaders[i].getName()))
+ return false;
+ if (!oneHeaders[i].getValue().equals(twoHeaders[i].getValue()))
+ return false;
+ }
+
+ return true;
+ }
+
+ private byte[] resourceToBytes(Resource res) throws IOException {
+ InputStream inputStream = res.getInputStream();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ int readBytes;
+ byte[] bytes = new byte[8096];
+ while ((readBytes = inputStream.read(bytes)) > 0) {
+ outputStream.write(bytes, 0, readBytes);
+ }
+
+ byte[] byteData = outputStream.toByteArray();
+
+ inputStream.close();
+ outputStream.close();
+
+ return byteData;
+ }
+}
Index: httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
===================================================================
--- httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java (revision 987194)
+++ httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java (working copy)
@@ -26,49 +26,75 @@
*/
package org.apache.http.impl.client.cache.ehcache;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
+import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
public class EhcacheHttpCacheStorage implements HttpCacheStorage {
private final Ehcache cache;
-
+ private final HttpCacheEntrySerializer serializer;
+
public EhcacheHttpCacheStorage(Ehcache cache) {
- this.cache = cache;
+ this(cache, new DefaultHttpCacheEntrySerializer());
}
+
+ public EhcacheHttpCacheStorage(Ehcache cache, HttpCacheEntrySerializer serializer){
+ this.cache = cache;
+ this.serializer = serializer;
+ }
public synchronized void putEntry(String key, HttpCacheEntry entry) throws IOException {
- cache.put(new Element(key, entry));
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ serializer.writeTo(entry, bos);
+ cache.put(new Element(key, bos.toByteArray()));
}
- public synchronized HttpCacheEntry getEntry(String url) {
- Element e = cache.get(url);
- return (e != null) ? (HttpCacheEntry)e.getValue() : null;
+ public synchronized HttpCacheEntry getEntry(String key) throws IOException {
+ Element e = cache.get(key);
+ if(e == null){
+ return null;
+ }
+
+ byte[] data = (byte[])e.getValue();
+ return serializer.readFrom(new ByteArrayInputStream(data));
}
- public synchronized void removeEntry(String url) {
- cache.remove(url);
+ public synchronized void removeEntry(String key) {
+ cache.remove(key);
}
public synchronized void updateEntry(String key, HttpCacheUpdateCallback callback)
- throws IOException {
- Element e = cache.get(key);
- HttpCacheEntry existingEntry = (e != null) ? (HttpCacheEntry)e.getValue() : null;
+ throws IOException {
+ Element oldElement = cache.get(key);
+
+ HttpCacheEntry existingEntry = null;
+ if(oldElement != null){
+ byte[] data = (byte[])oldElement.getValue();
+ existingEntry = serializer.readFrom(new ByteArrayInputStream(data));
+ }
+
HttpCacheEntry updatedEntry = callback.update(existingEntry);
-
- if (e == null) {
+
+ if (existingEntry == null) {
putEntry(key, updatedEntry);
} else {
// Attempt to do a CAS replace, if we fail throw an IOException for now
// While this operation should work fine within this instance, multiple instances
// could trample each others' data
- if (!cache.replace(e, new Element(key, updatedEntry))) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ serializer.writeTo(updatedEntry, bos);
+ Element newElement = new Element(key, bos.toByteArray());
+ if (!cache.replace(oldElement, newElement)) {
throw new IOException();
}
}
Index: httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java
===================================================================
--- httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java (revision 0)
+++ httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java (revision 0)
@@ -0,0 +1,146 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Set;
+
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
+import org.apache.http.client.cache.Resource;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.message.BasicStatusLine;
+
+/**
+ * {@link HttpCacheEntrySerializer} implementation that uses the default (native)
+ * serialization.
+ *
+ * @see java.io.Serializable
+ *
+ * @since 4.1
+ */
+@Immutable
+public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer {
+
+ /**
+ *
+ * @param cacheEntry
+ * @param os
+ * @throws IOException
+ */
+ public void writeTo(HttpCacheEntry cacheEntry, OutputStream os) throws IOException {
+
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(os);
+
+ oos.writeObject(cacheEntry.getRequestDate());
+ oos.writeObject(cacheEntry.getResponseDate());
+
+ // workaround to nonserializable BasicStatusLine object
+ // TODO: can change to directly serialize once new httpcore is released
+ oos.writeObject(cacheEntry.getStatusLine().getProtocolVersion());
+ oos.writeObject(cacheEntry.getStatusLine().getStatusCode());
+ oos.writeObject(cacheEntry.getStatusLine().getReasonPhrase());
+
+ // workaround to nonserializable BasicHeader object
+ // TODO: can change to directly serialize once new httpcore is released
+ Header[] headers = cacheEntry.getAllHeaders();
+ NameValuePair[] headerNvps = new NameValuePair[headers.length];
+ for(int i=0; i variants = (Set)ois.readObject();
+
+ return new HttpCacheEntry(requestDate, responseDate, statusLine, headers, resource, variants);
+ } catch (ClassNotFoundException cnfe) {
+ // CacheEntry should be known, it not we have a runtime issue
+ throw new RuntimeException(cnfe);
+ } finally {
+ if (ois != null) {
+ ois.close();
+ }
+ is.close();
+ }
+
+ }
+
+}
Index: httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
===================================================================
--- httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java (revision 0)
+++ httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java (revision 0)
@@ -0,0 +1,11 @@
+package org.apache.http.client.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public interface HttpCacheEntrySerializer {
+ public void writeTo(HttpCacheEntry entry, OutputStream os) throws IOException;
+
+ public HttpCacheEntry readFrom(InputStream is) throws IOException;
+}