You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2017/12/20 10:49:28 UTC

[3/3] httpcomponents-client git commit: New APIs for cache entry bulk retrieval; bulk retrieval support by Memcached storage implementation

New APIs for cache entry bulk retrieval; bulk retrieval support by Memcached storage implementation


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/29666a1a
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/29666a1a
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/29666a1a

Branch: refs/heads/master
Commit: 29666a1ad4a6c3b56cfec740ccc8093f0ad57812
Parents: 002f40f
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Fri Dec 15 14:06:23 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Wed Dec 20 11:46:57 2017 +0100

----------------------------------------------------------------------
 .../http/cache/HttpAsyncCacheStorage.java       |  13 +++
 .../cache/HttpAsyncCacheStorageAdaptor.java     |  14 +++
 .../hc/client5/http/cache/HttpCacheStorage.java |  15 +++
 .../AbstractSerializingAsyncCacheStorage.java   |  52 +++++++++
 .../cache/AbstractSerializingCacheStorage.java  |  29 ++++-
 .../http/impl/cache/BasicHttpCacheStorage.java  |  20 +++-
 .../impl/cache/ManagedHttpCacheStorage.java     |  16 +++
 .../cache/ehcache/EhcacheHttpCacheStorage.java  |  16 +++
 .../MemcachedHttpAsyncCacheStorage.java         |  31 ++++++
 .../memcached/MemcachedHttpCacheStorage.java    |  13 +++
 .../http/impl/cache/SimpleHttpCacheStorage.java |  15 ++-
 ...estAbstractSerializingAsyncCacheStorage.java | 107 +++++++++++++++++++
 .../TestAbstractSerializingCacheStorage.java    |  86 ++++++++++++++-
 13 files changed, 423 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorage.java
index 4ef0ce7..72064b3 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorage.java
@@ -26,6 +26,9 @@
  */
 package org.apache.hc.client5.http.cache;
 
+import java.util.Collection;
+import java.util.Map;
+
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.FutureCallback;
 
@@ -87,4 +90,14 @@ public interface HttpAsyncCacheStorage {
     Cancellable updateEntry(
             String key, HttpCacheCASOperation casOperation, FutureCallback<Boolean> callback);
 
+
+    /**
+     * Retrieves multiple cache entries stored under the given keys. Some implementations
+     * may use a single bulk operation to do the retrieval.
+     *
+     * @param keys cache keys
+     * @param callback result callback
+     */
+    Cancellable getEntries(Collection<String> keys, FutureCallback<Map<String, HttpCacheEntry>> callback);
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorageAdaptor.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorageAdaptor.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorageAdaptor.java
index c991fd7..d5199af 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorageAdaptor.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheStorageAdaptor.java
@@ -26,6 +26,9 @@
  */
 package org.apache.hc.client5.http.cache;
 
+import java.util.Collection;
+import java.util.Map;
+
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.util.Args;
@@ -95,4 +98,15 @@ public final class HttpAsyncCacheStorageAdaptor implements HttpAsyncCacheStorage
         return NOOP_CANCELLABLE;
     }
 
+    public Cancellable getEntries(final Collection<String> keys, final FutureCallback<Map<String, HttpCacheEntry>> callback) {
+        Args.notNull(keys, "Key");
+        Args.notNull(callback, "Callback");
+        try {
+            callback.completed(cacheStorage.getEntries(keys));
+        } catch (final Exception ex) {
+            callback.failed(ex);
+        }
+        return NOOP_CANCELLABLE;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheStorage.java
index e58efcb..b4a459e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheStorage.java
@@ -26,6 +26,9 @@
  */
 package org.apache.hc.client5.http.cache;
 
+import java.util.Collection;
+import java.util.Map;
+
 /**
  * {@literal HttpCacheStorage} represents an abstract HTTP cache
  * storage backend that can then be plugged into the classic
@@ -72,4 +75,16 @@ public interface HttpCacheStorage {
     void updateEntry(
             String key, HttpCacheCASOperation casOperation) throws ResourceIOException, HttpCacheUpdateException;
 
+
+    /**
+     * Retrieves multiple cache entries stored under the given keys. Some implementations
+     * may use a single bulk operation to do the retrieval.
+     *
+     * @param keys cache keys
+     * @return an map of {@link HttpCacheEntry}s.
+     *
+     * @since 5.0
+     */
+    Map<String, HttpCacheEntry> getEntries(Collection<String> keys) throws ResourceIOException;
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingAsyncCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingAsyncCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingAsyncCacheStorage.java
index 36c055f..69eb18c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingAsyncCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingAsyncCacheStorage.java
@@ -26,6 +26,11 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
@@ -68,6 +73,8 @@ public abstract class AbstractSerializingAsyncCacheStorage<T, CAS> implements Ht
 
     protected abstract Cancellable delete(String storageKey, FutureCallback<Boolean> callback);
 
+    protected abstract Cancellable bulkRestore(Collection<String> storageKeys, FutureCallback<Map<String, T>> callback);
+
     @Override
     public final Cancellable putEntry(
             final String key, final HttpCacheEntry entry, final FutureCallback<Boolean> callback) {
@@ -225,4 +232,49 @@ public abstract class AbstractSerializingAsyncCacheStorage<T, CAS> implements Ht
         }
     }
 
+    @Override
+    public final Cancellable getEntries(final Collection<String> keys, final FutureCallback<Map<String, HttpCacheEntry>> callback) {
+        Args.notNull(keys, "Storage keys");
+        Args.notNull(callback, "Callback");
+        try {
+            final List<String> storageKeys = new ArrayList<>(keys.size());
+            for (final String key: keys) {
+                storageKeys.add(digestToStorageKey(key));
+            }
+            return bulkRestore(storageKeys, new FutureCallback<Map<String, T>>() {
+
+                @Override
+                public void completed(final Map<String, T> storageObjects) {
+                    try {
+                        final Map<String, HttpCacheEntry> resultMap = new HashMap<>();
+                        for (final Map.Entry<String, T> storageEntry: storageObjects.entrySet()) {
+                            final String key = storageEntry.getKey();
+                            final HttpCacheStorageEntry entry = serializer.deserialize(storageEntry.getValue());
+                            if (key.equals(entry.getKey())) {
+                                resultMap.put(key, entry.getContent());
+                            }
+                        }
+                        callback.completed(resultMap);
+                    } catch (final Exception ex) {
+                        callback.failed(ex);
+                    }
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    callback.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    callback.cancelled();
+                }
+
+            });
+        } catch (final Exception ex) {
+            callback.failed(ex);
+            return NOOP_CANCELLABLE;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java
index 6e59bb8..4a5ae98 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java
@@ -26,11 +26,17 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
-import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.core5.util.Args;
@@ -64,6 +70,8 @@ public abstract class AbstractSerializingCacheStorage<T, CAS> implements HttpCac
 
     protected abstract void delete(String storageKey) throws ResourceIOException;
 
+    protected abstract Map<String, T> bulkRestore(Collection<String> storageKeys) throws ResourceIOException;
+
     @Override
     public final void putEntry(final String key, final HttpCacheEntry entry) throws ResourceIOException {
         final String storageKey = digestToStorageKey(key);
@@ -124,4 +132,23 @@ public abstract class AbstractSerializingCacheStorage<T, CAS> implements HttpCac
         }
     }
 
+    @Override
+    public final Map<String, HttpCacheEntry> getEntries(final Collection<String> keys) throws ResourceIOException {
+        Args.notNull(keys, "Storage keys");
+        final List<String> storageKeys = new ArrayList<>(keys.size());
+        for (final String key: keys) {
+            storageKeys.add(digestToStorageKey(key));
+        }
+        final Map<String, T> storageObjectMap = bulkRestore(storageKeys);
+        final Map<String, HttpCacheEntry> resultMap = new HashMap<>();
+        for (final Map.Entry<String, T> storageEntry: storageObjectMap.entrySet()) {
+            final String key = storageEntry.getKey();
+            final HttpCacheStorageEntry entry = serializer.deserialize(storageEntry.getValue());
+            if (key.equals(entry.getKey())) {
+                resultMap.put(key, entry.getContent());
+            }
+        }
+        return resultMap;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCacheStorage.java
index e908de2..e02be0f 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCacheStorage.java
@@ -26,12 +26,17 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
-import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.Args;
 
 /**
  * Basic {@link HttpCacheStorage} implementation backed by an instance of
@@ -97,4 +102,17 @@ public class BasicHttpCacheStorage implements HttpCacheStorage {
         entries.put(url, casOperation.execute(existingEntry));
     }
 
+    @Override
+    public Map<String, HttpCacheEntry> getEntries(final Collection<String> keys) throws ResourceIOException {
+        Args.notNull(keys, "Key");
+        final Map<String, HttpCacheEntry> resultMap = new HashMap<>(keys.size());
+        for (final String key: keys) {
+            final HttpCacheEntry entry = getEntry(key);
+            if (entry != null) {
+                resultMap.put(key, entry);
+            }
+        }
+        return resultMap;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ManagedHttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ManagedHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ManagedHttpCacheStorage.java
index 952e40c..a89c667 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ManagedHttpCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ManagedHttpCacheStorage.java
@@ -28,7 +28,10 @@ package org.apache.hc.client5.http.impl.cache;
 
 import java.io.Closeable;
 import java.lang.ref.ReferenceQueue;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -150,6 +153,19 @@ public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable {
         }
     }
 
+    @Override
+    public Map<String, HttpCacheEntry> getEntries(final Collection<String> keys) throws ResourceIOException {
+        Args.notNull(keys, "Key");
+        final Map<String, HttpCacheEntry> resultMap = new HashMap<>(keys.size());
+        for (final String key: keys) {
+            final HttpCacheEntry entry = getEntry(key);
+            if (entry != null) {
+                resultMap.put(key, entry);
+            }
+        }
+        return resultMap;
+    }
+
     public void cleanResources() {
         if (this.active.get()) {
             ResourceReference ref;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java
index c3d7a38..1053bdb 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java
@@ -26,6 +26,10 @@
  */
 package org.apache.hc.client5.http.impl.cache.ehcache;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
 import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
 import org.apache.hc.client5.http.cache.ResourceIOException;
@@ -131,4 +135,16 @@ public class EhcacheHttpCacheStorage<T> extends AbstractSerializingCacheStorage<
         cache.remove(storageKey);
     }
 
+    @Override
+    protected Map<String, T> bulkRestore(final Collection<String> storageKeys) throws ResourceIOException {
+        final Map<String, T> resultMap = new HashMap<>();
+        for (final String storageKey: storageKeys) {
+            final T storageObject = cache.get(storageKey);
+            if (storageObject != null) {
+                resultMap.put(storageKey, storageObject);
+            }
+        }
+        return resultMap;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java
index 478c578..296b85a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java
@@ -28,6 +28,9 @@ package org.apache.hc.client5.http.impl.cache.memcached;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
@@ -42,6 +45,9 @@ import org.apache.hc.core5.util.Args;
 import net.spy.memcached.CASResponse;
 import net.spy.memcached.CASValue;
 import net.spy.memcached.MemcachedClient;
+import net.spy.memcached.internal.BulkFuture;
+import net.spy.memcached.internal.BulkGetCompletionListener;
+import net.spy.memcached.internal.BulkGetFuture;
 import net.spy.memcached.internal.GetCompletionListener;
 import net.spy.memcached.internal.GetFuture;
 import net.spy.memcached.internal.OperationCompletionListener;
@@ -247,4 +253,29 @@ public class MemcachedHttpAsyncCacheStorage extends AbstractBinaryAsyncCacheStor
         return operation(client.delete(storageKey), callback);
     }
 
+    @Override
+    protected Cancellable bulkRestore(final Collection<String> storageKeys, final FutureCallback<Map<String, byte[]>> callback) {
+        final BulkFuture<Map<String, Object>> future = client.asyncGetBulk(storageKeys);
+        future.addListener(new BulkGetCompletionListener() {
+
+            @Override
+            public void onComplete(final BulkGetFuture<?> future) throws Exception {
+                final Map<String, ?> storageObjectMap = future.get();
+                final Map<String, byte[]> resultMap = new HashMap<>(storageObjectMap.size());
+                for (final Map.Entry<String, ?> resultEntry: storageObjectMap.entrySet()) {
+                    resultMap.put(resultEntry.getKey(), castAsByteArray(resultEntry.getValue()));
+                }
+                callback.completed(resultMap);
+            }
+        });
+        return new Cancellable() {
+
+            @Override
+            public boolean cancel() {
+                return future.cancel(true);
+            }
+
+        };
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java
index 6a6195c..7df45b9 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java
@@ -28,6 +28,9 @@ package org.apache.hc.client5.http.impl.cache.memcached;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
 import org.apache.hc.client5.http.cache.ResourceIOException;
@@ -182,4 +185,14 @@ public class MemcachedHttpCacheStorage extends AbstractBinaryCacheStorage<CASVal
         client.delete(storageKey);
     }
 
+    @Override
+    protected Map<String, byte[]> bulkRestore(final Collection<String> storageKeys) throws ResourceIOException {
+        final Map<String, ?> storageObjectMap = client.getBulk(storageKeys);
+        final Map<String, byte[]> resultMap = new HashMap<>(storageObjectMap.size());
+        for (final Map.Entry<String, ?> resultEntry: storageObjectMap.entrySet()) {
+            resultMap.put(resultEntry.getKey(), castAsByteArray(resultEntry.getValue()));
+        }
+        return resultMap;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/SimpleHttpCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/SimpleHttpCacheStorage.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/SimpleHttpCacheStorage.java
index 0340e79..8dcfad7 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/SimpleHttpCacheStorage.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/SimpleHttpCacheStorage.java
@@ -26,12 +26,13 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
-import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 
 class SimpleHttpCacheStorage implements HttpCacheStorage {
@@ -65,4 +66,16 @@ class SimpleHttpCacheStorage implements HttpCacheStorage {
         map.put(key,v2);
     }
 
+    @Override
+    public Map<String, HttpCacheEntry> getEntries(final Collection<String> keys) throws ResourceIOException {
+        final Map<String, HttpCacheEntry> resultMap = new HashMap<>(keys.size());
+        for (final String key: keys) {
+            final HttpCacheEntry entry = getEntry(key);
+            if (entry != null) {
+                resultMap.put(key, entry);
+            }
+        }
+        return resultMap;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java
index 7070c89..8e5455e 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java
@@ -26,6 +26,13 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
@@ -57,6 +64,8 @@ public class TestAbstractSerializingAsyncCacheStorage {
     private FutureCallback<Boolean> operationCallback;
     @Mock
     private FutureCallback<HttpCacheEntry> cacheEntryCallback;
+    @Mock
+    private FutureCallback<Map<String, HttpCacheEntry>> bulkCacheEntryCallback;
 
     private AbstractBinaryAsyncCacheStorage<String> impl;
 
@@ -447,4 +456,102 @@ public class TestAbstractSerializingAsyncCacheStorage {
         Mockito.verify(operationCallback).failed(Mockito.<HttpCacheUpdateException>any());
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testBulkGet() throws Exception {
+        final String key1 = "foo this";
+        final String key2 = "foo that";
+        final String storageKey1 = "bar this";
+        final String storageKey2 = "bar that";
+        final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
+        final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
+
+        when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
+        when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
+
+        when(impl.bulkRestore(
+                Mockito.<String>anyCollection(),
+                Mockito.<FutureCallback<Map<String, byte[]>>>any())).thenAnswer(new Answer<Cancellable>() {
+
+            @Override
+            public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
+                final Collection<String> keys = invocation.getArgument(0);
+                final FutureCallback<Map<String, byte[]>> callback = invocation.getArgument(1);
+                final Map<String, byte[]> resultMap = new HashMap<>();
+                if (keys.contains(storageKey1)) {
+                    resultMap.put(storageKey1, serialize(key1, value1));
+                }
+                if (keys.contains(storageKey2)) {
+                    resultMap.put(storageKey2, serialize(key2, value2));
+                }
+                callback.completed(resultMap);
+                return cancellable;
+            }
+        });
+
+        impl.getEntries(Arrays.asList(key1, key2), bulkCacheEntryCallback);
+        final ArgumentCaptor<Map<String, HttpCacheEntry>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
+        Mockito.verify(bulkCacheEntryCallback).completed(argumentCaptor.capture());
+
+        final Map<String, HttpCacheEntry> entryMap = argumentCaptor.getValue();
+        Assert.assertThat(entryMap, CoreMatchers.notNullValue());
+        Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
+        Assert.assertThat(entryMap.get(key2), HttpCacheEntryMatcher.equivalent(value2));
+
+        verify(impl).digestToStorageKey(key1);
+        verify(impl).digestToStorageKey(key2);
+        verify(impl).bulkRestore(
+                Mockito.eq(Arrays.asList(storageKey1, storageKey2)),
+                Mockito.<FutureCallback<Map<String, byte[]>>>any());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testBulkGetKeyMismatch() throws Exception {
+        final String key1 = "foo this";
+        final String key2 = "foo that";
+        final String storageKey1 = "bar this";
+        final String storageKey2 = "bar that";
+        final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
+        final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
+
+        when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
+        when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
+
+        when(impl.bulkRestore(
+                Mockito.<String>anyCollection(),
+                Mockito.<FutureCallback<Map<String, byte[]>>>any())).thenAnswer(new Answer<Cancellable>() {
+
+            @Override
+            public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
+                final Collection<String> keys = invocation.getArgument(0);
+                final FutureCallback<Map<String, byte[]>> callback = invocation.getArgument(1);
+                final Map<String, byte[]> resultMap = new HashMap<>();
+                if (keys.contains(storageKey1)) {
+                    resultMap.put(storageKey1, serialize(key1, value1));
+                }
+                if (keys.contains(storageKey2)) {
+                    resultMap.put(storageKey2, serialize("not foo", value2));
+                }
+                callback.completed(resultMap);
+                return cancellable;
+            }
+        });
+
+        impl.getEntries(Arrays.asList(key1, key2), bulkCacheEntryCallback);
+        final ArgumentCaptor<Map<String, HttpCacheEntry>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
+        Mockito.verify(bulkCacheEntryCallback).completed(argumentCaptor.capture());
+
+        final Map<String, HttpCacheEntry> entryMap = argumentCaptor.getValue();
+        Assert.assertThat(entryMap, CoreMatchers.notNullValue());
+        Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
+        Assert.assertThat(entryMap.get(key2), CoreMatchers.nullValue());
+
+        verify(impl).digestToStorageKey(key1);
+        verify(impl).digestToStorageKey(key2);
+        verify(impl).bulkRestore(
+                Mockito.eq(Arrays.asList(storageKey1, storageKey2)),
+                Mockito.<FutureCallback<Map<String, byte[]>>>any());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/29666a1a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java
index 2becb4a..0537be7 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java
@@ -30,9 +30,14 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
-import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.hamcrest.CoreMatchers;
@@ -42,6 +47,8 @@ import org.junit.Test;
 import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 @SuppressWarnings("boxing") // test code
 public class TestAbstractSerializingCacheStorage {
@@ -253,4 +260,81 @@ public class TestAbstractSerializingCacheStorage {
         verify(impl, Mockito.times(3)).getStorageObject("stuff");
         verify(impl, Mockito.times(3)).updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any());
     }
+
+    @Test
+    public void testBulkGet() throws Exception {
+        final String key1 = "foo this";
+        final String key2 = "foo that";
+        final String storageKey1 = "bar this";
+        final String storageKey2 = "bar that";
+        final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
+        final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
+
+        when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
+        when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
+
+        when(impl.bulkRestore(Mockito.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
+
+            @Override
+            public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
+                final Collection<String> keys = invocation.getArgument(0);
+                final Map<String, byte[]> resultMap = new HashMap<>();
+                if (keys.contains(storageKey1)) {
+                    resultMap.put(storageKey1, serialize(key1, value1));
+                }
+                if (keys.contains(storageKey2)) {
+                    resultMap.put(storageKey2, serialize(key2, value2));
+                }
+                return resultMap;
+            }
+        });
+
+        final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
+        Assert.assertThat(entryMap, CoreMatchers.notNullValue());
+        Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
+        Assert.assertThat(entryMap.get(key2), HttpCacheEntryMatcher.equivalent(value2));
+
+        verify(impl).digestToStorageKey(key1);
+        verify(impl).digestToStorageKey(key2);
+        verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
+    }
+
+    @Test
+    public void testBulkGetKeyMismatch() throws Exception {
+        final String key1 = "foo this";
+        final String key2 = "foo that";
+        final String storageKey1 = "bar this";
+        final String storageKey2 = "bar that";
+        final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
+        final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
+
+        when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
+        when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
+
+        when(impl.bulkRestore(Mockito.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
+
+            @Override
+            public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
+                final Collection<String> keys = invocation.getArgument(0);
+                final Map<String, byte[]> resultMap = new HashMap<>();
+                if (keys.contains(storageKey1)) {
+                    resultMap.put(storageKey1, serialize(key1, value1));
+                }
+                if (keys.contains(storageKey2)) {
+                    resultMap.put(storageKey2, serialize("not foo", value2));
+                }
+                return resultMap;
+            }
+        });
+
+        final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
+        Assert.assertThat(entryMap, CoreMatchers.notNullValue());
+        Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
+        Assert.assertThat(entryMap.get(key2), CoreMatchers.nullValue());
+
+        verify(impl).digestToStorageKey(key1);
+        verify(impl).digestToStorageKey(key2);
+        verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
+    }
+
 }