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 2012/07/29 23:47:03 UTC

svn commit: r1366958 - in /httpcomponents/httpasyncclient/trunk: ./ httpasyncclient-cache/ httpasyncclient-cache/src/ httpasyncclient-cache/src/main/ httpasyncclient-cache/src/main/java/ httpasyncclient-cache/src/main/java/org/ httpasyncclient-cache/sr...

Author: olegk
Date: Sun Jul 29 21:47:02 2012
New Revision: 1366958

URL: http://svn.apache.org/viewvc?rev=1366958&view=rev
Log:
HTTPASYNC-17: Caching HttpAsyncClient facade.
Contributed by Clinton Nielsen <clinton.h.nielsen at gmail.com>

Added:
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml   (with props)
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java   (with props)
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java   (with props)
    httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java   (with props)
Modified:
    httpcomponents/httpasyncclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpasyncclient/trunk/pom.xml

Modified: httpcomponents/httpasyncclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/RELEASE_NOTES.txt?rev=1366958&r1=1366957&r2=1366958&view=diff
==============================================================================
--- httpcomponents/httpasyncclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpasyncclient/trunk/RELEASE_NOTES.txt Sun Jul 29 21:47:02 2012
@@ -1,6 +1,9 @@
 Changes sicne release 4.0 Beta 1
 -------------------
 
+* [HTTPASYNC-17] Caching HttpAsyncClient facade.
+  Contributed by Clinton Nielsen <clinton.h.nielsen at gmail.com>
+
 * [HTTPASYNC-19] Fixed incorrect execution of message exchanges that span across multiple hosts 
   (for instance, in case of a request redirect).  
   Contributed by Oleg Kalnichevski <olegk at apache.org>

Added: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml?rev=1366958&view=auto
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml (added)
+++ httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml Sun Jul 29 21:47:02 2012
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   ====================================================================
+   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
+   <http://www.apache.org />.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcomponents-asyncclient</artifactId>
+    <version>4.0-beta2-SNAPSHOT</version>
+  </parent>
+  <artifactId>httpasyncclient-cache</artifactId>
+  <name>HttpAsyncClient Cache</name>
+  <description>
+   HttpComponents AsyncClient Cache
+  </description>
+  <url>http://hc.apache.org/httpcomponents-asyncclient</url>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpasyncclient</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient-cache</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <maven.compile.source>1.5</maven.compile.source>
+    <maven.compile.target>1.5</maven.compile.target>
+    <maven.compile.optimize>true</maven.compile.optimize>
+    <maven.compile.deprecation>true</maven.compile.deprecation>
+  </properties>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <includes>
+            <include>**/*.properties</include>
+        </includes>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>${maven.compile.source}</source>
+          <target>${maven.compile.target}</target>
+          <optimize>${maven.compile.optimize}</optimize>
+          <showDeprecations>${maven.compile.deprecation}</showDeprecations>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>com.atlassian.maven.plugins</groupId>
+        <artifactId>maven-clover2-plugin</artifactId>
+        <configuration>
+          <flushPolicy>threaded</flushPolicy>
+          <flushInterval>100</flushInterval>
+          <targetPercentage>50%</targetPercentage>
+        </configuration>
+        <executions>
+          <execution>
+            <id>site</id>
+            <phase>pre-site</phase>
+            <goals>
+              <goal>instrument</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>${hc.javadoc.version}</version>
+        <configuration>
+          <source>1.5</source>
+          <links>
+            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+            <link>http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/</link>
+            <link>http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/</link>
+          </links>
+        </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>javadoc</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+
+      <plugin>
+        <groupId>com.atlassian.maven.plugins</groupId>
+        <artifactId>maven-clover2-plugin</artifactId>
+        <version>${clover.version}</version>
+        <configuration>
+          <jdk>1.5</jdk>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-jxr-plugin</artifactId>
+         <version>${hc.jxr.version}</version>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+         <version>${hc.surefire-report.version}</version>
+      </plugin>
+
+    </plugins>
+  </reporting>
+
+</project>

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/pom.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java?rev=1366958&view=auto
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java (added)
+++ httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java Sun Jul 29 21:47:02 2012
@@ -0,0 +1,101 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+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.ProtocolException;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Class used to represent an asynchronous revalidation event, such as with
+ * "stale-while-revalidate"
+ */
+class AsynchronousAsyncValidationRequest implements Runnable {
+    private final AsynchronousAsyncValidator parent;
+    private final CachingHttpAsyncClient cachingAsyncClient;
+    private final HttpHost target;
+    private final HttpRequest request;
+    private final HttpContext context;
+    private final HttpCacheEntry cacheEntry;
+    private final String identifier;
+
+    private final Log log = LogFactory.getLog(getClass());
+
+    /**
+     * Used internally by {@link AsynchronousValidator} to schedule a
+     * revalidation.
+     */
+    AsynchronousAsyncValidationRequest(AsynchronousAsyncValidator parent,
+            CachingHttpAsyncClient cachingClient, HttpHost target, HttpRequest request,
+            HttpContext context, HttpCacheEntry cacheEntry, String identifier) {
+        this.parent = parent;
+        this.cachingAsyncClient = cachingClient;
+        this.target = target;
+        this.request = request;
+        this.context = context;
+        this.cacheEntry = cacheEntry;
+        this.identifier = identifier;
+    }
+
+    public void run() {
+        try {
+            this.cachingAsyncClient.revalidateCacheEntry(this.target, this.request, this.context,
+                    this.cacheEntry, new FutureCallback<HttpResponse>() {
+
+                        public void cancelled() {
+                        }
+
+                        public void completed(HttpResponse httpResponse) {
+                        }
+
+                        public void failed(Exception e) {
+                            if (e instanceof IOException) {
+                                AsynchronousAsyncValidationRequest.this.log
+                                        .debug("Asynchronous revalidation failed due to exception: "
+                                                + e);
+                            }
+                        }
+                    });
+        } catch (ProtocolException pe) {
+            this.log.error("ProtocolException thrown during asynchronous revalidation: " + pe);
+        } finally {
+            this.parent.markComplete(this.identifier);
+        }
+    }
+
+    String getIdentifier() {
+        return this.identifier;
+    }
+
+}

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidationRequest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java?rev=1366958&view=auto
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java (added)
+++ httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java Sun Jul 29 21:47:02 2012
@@ -0,0 +1,141 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+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.client.cache.HttpCacheEntry;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Class used for asynchronous revalidations to be used when the "stale-
+ * while-revalidate" directive is present
+ */
+class AsynchronousAsyncValidator {
+    private final CachingHttpAsyncClient cachingAsyncClient;
+    private final ExecutorService executor;
+    private final Set<String> queued;
+    private final CacheKeyGenerator cacheKeyGenerator;
+
+    private final Log log = LogFactory.getLog(getClass());
+
+    /**
+     * Create AsynchronousValidator which will make revalidation requests using
+     * the supplied {@link CachingHttpClient}, and a {@link ThreadPoolExecutor}
+     * generated according to the thread pool settings provided in the given
+     * {@link CacheConfig}.
+     *
+     * @param cachingClient
+     *            used to execute asynchronous requests
+     * @param config
+     *            specifies thread pool settings. See
+     *            {@link CacheConfig#getAsynchronousWorkersMax()},
+     *            {@link CacheConfig#getAsynchronousWorkersCore()},
+     *            {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
+     *            and {@link CacheConfig#getRevalidationQueueSize()}.
+     */
+    public AsynchronousAsyncValidator(CachingHttpAsyncClient cachingClient, CacheConfig config) {
+        this(cachingClient, new ThreadPoolExecutor(config.getAsynchronousWorkersCore(),
+                config.getAsynchronousWorkersMax(), config.getAsynchronousWorkerIdleLifetimeSecs(),
+                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(
+                        config.getRevalidationQueueSize())));
+    }
+
+    /**
+     * Create AsynchronousValidator which will make revalidation requests using
+     * the supplied {@link CachingHttpClient} and {@link ExecutorService}.
+     *
+     * @param cachingClient
+     *            used to execute asynchronous requests
+     * @param executor
+     *            used to manage a thread pool of revalidation workers
+     */
+    AsynchronousAsyncValidator(CachingHttpAsyncClient cachingClient, ExecutorService executor) {
+        this.cachingAsyncClient = cachingClient;
+        this.executor = executor;
+        this.queued = new HashSet<String>();
+        this.cacheKeyGenerator = new CacheKeyGenerator();
+    }
+
+    /**
+     * Schedules an asynchronous revalidation
+     *
+     * @param target
+     * @param request
+     * @param context
+     * @param entry
+     */
+    public synchronized void revalidateCacheEntry(HttpHost target, HttpRequest request,
+            HttpContext context, HttpCacheEntry entry) {
+        // getVariantURI will fall back on getURI if no variants exist
+        String uri = this.cacheKeyGenerator.getVariantURI(target, request, entry);
+
+        if (!this.queued.contains(uri)) {
+            AsynchronousAsyncValidationRequest asyncRevalidationRequest = new AsynchronousAsyncValidationRequest(
+                    this, this.cachingAsyncClient, target, request, context, entry, uri);
+
+            try {
+                this.executor.execute(asyncRevalidationRequest);
+                this.queued.add(uri);
+            } catch (RejectedExecutionException ree) {
+                this.log.debug("Revalidation for [" + uri + "] not scheduled: " + ree);
+            }
+        }
+    }
+
+    /**
+     * Removes an identifier from the internal list of revalidation jobs in
+     * progress. This is meant to be called by
+     * {@link AsynchronousValidationRequest#run()} once the revalidation is
+     * complete, using the identifier passed in during constructions.
+     *
+     * @param identifier
+     */
+    synchronized void markComplete(String identifier) {
+        this.queued.remove(identifier);
+    }
+
+    Set<String> getScheduledIdentifiers() {
+        return Collections.unmodifiableSet(this.queued);
+    }
+
+    ExecutorService getExecutor() {
+        return this.executor;
+    }
+
+}

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousAsyncValidator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java?rev=1366958&view=auto
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java (added)
+++ httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java Sun Jul 29 21:47:02 2012
@@ -0,0 +1,1002 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpMessage;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.RequestLine;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.cache.CacheResponseStatus;
+import org.apache.http.client.cache.HeaderConstants;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.cache.ResourceFactory;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.concurrent.BasicFuture;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.impl.cookie.DateParseException;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.impl.nio.client.DefaultHttpAsyncClient;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.nio.client.HttpAsyncClient;
+import org.apache.http.nio.conn.ClientAsyncConnectionManager;
+import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
+import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
+import org.apache.http.nio.reactor.IOReactorException;
+import org.apache.http.nio.reactor.IOReactorStatus;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.VersionInfo;
+
+@ThreadSafe // So long as the responseCache implementation is threadsafe
+public class CachingHttpAsyncClient implements HttpAsyncClient {
+
+    public static final String CACHE_RESPONSE_STATUS = "http.cache.response.status";
+
+    private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
+
+    private final AtomicLong cacheHits = new AtomicLong();
+    private final AtomicLong cacheMisses = new AtomicLong();
+    private final AtomicLong cacheUpdates = new AtomicLong();
+
+    private final HttpAsyncClient backend;
+    private final HttpCache responseCache;
+    private final CacheValidityPolicy validityPolicy;
+    private final ResponseCachingPolicy responseCachingPolicy;
+    private final CachedHttpResponseGenerator responseGenerator;
+    private final CacheableRequestPolicy cacheableRequestPolicy;
+    private final CachedResponseSuitabilityChecker suitabilityChecker;
+
+    private final ConditionalRequestBuilder conditionalRequestBuilder;
+
+    private final long maxObjectSizeBytes;
+    private final boolean sharedCache;
+
+    private final ResponseProtocolCompliance responseCompliance;
+    private final RequestProtocolCompliance requestCompliance;
+
+    private final AsynchronousAsyncValidator asynchAsyncRevalidator;
+
+    private final Log log = LogFactory.getLog(getClass());
+
+    CachingHttpAsyncClient(
+            HttpAsyncClient client,
+            HttpCache cache,
+            CacheConfig config) {
+        super();
+        if (client == null) {
+            throw new IllegalArgumentException("HttpClient may not be null");
+        }
+        if (cache == null) {
+            throw new IllegalArgumentException("HttpCache may not be null");
+        }
+        if (config == null) {
+            throw new IllegalArgumentException("CacheConfig may not be null");
+        }
+        this.maxObjectSizeBytes = config.getMaxObjectSize();
+        this.sharedCache = config.isSharedCache();
+        this.backend = client;
+        this.responseCache = cache;
+        this.validityPolicy = new CacheValidityPolicy();
+        this.responseCachingPolicy = new ResponseCachingPolicy(this.maxObjectSizeBytes, this.sharedCache);
+        this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
+        this.cacheableRequestPolicy = new CacheableRequestPolicy();
+        this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, config);
+        this.conditionalRequestBuilder = new ConditionalRequestBuilder();
+
+        this.responseCompliance = new ResponseProtocolCompliance();
+        this.requestCompliance = new RequestProtocolCompliance();
+
+        this.asynchAsyncRevalidator = makeAsynchronousValidator(config);
+    }
+
+    public CachingHttpAsyncClient() throws IOReactorException {
+        this(new DefaultHttpAsyncClient(),
+                new BasicHttpCache(),
+                new CacheConfig());
+    }
+
+    public CachingHttpAsyncClient(CacheConfig config) throws IOReactorException {
+        this(new DefaultHttpAsyncClient(),
+                new BasicHttpCache(config),
+                config);
+    }
+
+    public CachingHttpAsyncClient(HttpAsyncClient client) {
+        this(client,
+                new BasicHttpCache(),
+                new CacheConfig());
+    }
+
+    public CachingHttpAsyncClient(HttpAsyncClient client, CacheConfig config) {
+        this(client,
+                new BasicHttpCache(config),
+                config);
+    }
+
+    public CachingHttpAsyncClient(
+            HttpAsyncClient client,
+            ResourceFactory resourceFactory,
+            HttpCacheStorage storage,
+            CacheConfig config) {
+        this(client,
+                new BasicHttpCache(resourceFactory, storage, config),
+                config);
+    }
+
+    public CachingHttpAsyncClient(
+            HttpAsyncClient client,
+            HttpCacheStorage storage,
+            CacheConfig config) {
+        this(client,
+                new BasicHttpCache(new HeapResourceFactory(), storage, config),
+                config);
+    }
+
+    CachingHttpAsyncClient(
+            HttpAsyncClient backend,
+            CacheValidityPolicy validityPolicy,
+            ResponseCachingPolicy responseCachingPolicy,
+            HttpCache responseCache,
+            CachedHttpResponseGenerator responseGenerator,
+            CacheableRequestPolicy cacheableRequestPolicy,
+            CachedResponseSuitabilityChecker suitabilityChecker,
+            ConditionalRequestBuilder conditionalRequestBuilder,
+            ResponseProtocolCompliance responseCompliance,
+            RequestProtocolCompliance requestCompliance) {
+        CacheConfig config = new CacheConfig();
+        this.maxObjectSizeBytes = config.getMaxObjectSize();
+        this.sharedCache = config.isSharedCache();
+        this.backend = backend;
+        this.validityPolicy = validityPolicy;
+        this.responseCachingPolicy = responseCachingPolicy;
+        this.responseCache = responseCache;
+        this.responseGenerator = responseGenerator;
+        this.cacheableRequestPolicy = cacheableRequestPolicy;
+        this.suitabilityChecker = suitabilityChecker;
+        this.conditionalRequestBuilder = conditionalRequestBuilder;
+        this.responseCompliance = responseCompliance;
+        this.requestCompliance = requestCompliance;
+        this.asynchAsyncRevalidator = makeAsynchronousValidator(config);
+    }
+
+    private AsynchronousAsyncValidator makeAsynchronousValidator(
+            CacheConfig config) {
+        if (config.getAsynchronousWorkersMax() > 0) {
+            return new AsynchronousAsyncValidator(this, config);
+        }
+        return null;
+    }
+
+    /**
+     * Reports the number of times that the cache successfully responded
+     * to an {@link HttpRequest} without contacting the origin server.
+     * @return the number of cache hits
+     */
+    public long getCacheHits() {
+        return this.cacheHits.get();
+    }
+
+    /**
+     * Reports the number of times that the cache contacted the origin
+     * server because it had no appropriate response cached.
+     * @return the number of cache misses
+     */
+    public long getCacheMisses() {
+        return this.cacheMisses.get();
+    }
+
+    /**
+     * Reports the number of times that the cache was able to satisfy
+     * a response by revalidating an existing but stale cache entry.
+     * @return the number of cache revalidations
+     */
+    public long getCacheUpdates() {
+        return this.cacheUpdates.get();
+    }
+
+    public Future<HttpResponse> execute(
+            final HttpHost target,
+            final HttpRequest request,
+            final FutureCallback<HttpResponse> callback) {
+        return execute(target, request, null, callback);
+    }
+
+    public <T> Future<T> execute(
+            final HttpAsyncRequestProducer requestProducer,
+            final HttpAsyncResponseConsumer<T> responseConsumer,
+            final FutureCallback<T> callback) {
+        return execute(requestProducer, responseConsumer, null, callback);
+    }
+
+    public <T> Future<T> execute(
+            final HttpAsyncRequestProducer requestProducer,
+            final HttpAsyncResponseConsumer<T> responseConsumer,
+            final HttpContext context,
+            final FutureCallback<T> callback) {
+        this.log.warn("CachingHttpAsyncClient does not caching for streaming HTTP exchanges");
+        return this.backend.execute(requestProducer, responseConsumer, context, callback);
+    }
+
+    public Future<HttpResponse> execute(
+            final HttpUriRequest request,
+            final FutureCallback<HttpResponse> callback) {
+        return execute(request, null, callback);
+    }
+
+    public Future<HttpResponse> execute(
+            final HttpUriRequest request,
+            final HttpContext context,
+            final FutureCallback<HttpResponse> callback) {
+        URI uri = request.getURI();
+        HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+        return execute(httpHost, request, context, callback);
+    }
+
+    public ClientAsyncConnectionManager getConnectionManager() {
+        return this.backend.getConnectionManager();
+    }
+
+    public HttpParams getParams() {
+        return this.backend.getParams();
+    }
+
+    public Future<HttpResponse> execute(
+            final HttpHost target,
+            final HttpRequest request,
+            final HttpContext context,
+            final FutureCallback<HttpResponse> futureCallback) {
+        // default response context
+        setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
+
+        String via = generateViaHeader(request);
+
+        if (clientRequestsOurOptions(request)) {
+            setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.completed(new OptionsHttp11Response());
+            return future;
+        }
+
+        HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(
+                request, context);
+        if (fatalErrorResponse != null) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.completed(fatalErrorResponse);
+            return future;
+        }
+
+        HttpRequest httRequest = request;
+        try {
+            httRequest = this.requestCompliance.makeRequestCompliant(request);
+        } catch (ClientProtocolException e) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.failed(e);
+            return future;
+        }
+        httRequest.addHeader("Via",via);
+
+        flushEntriesInvalidatedByRequest(target, httRequest);
+
+        if (!this.cacheableRequestPolicy.isServableFromCache(httRequest)) {
+            return callBackend(target, httRequest, context, futureCallback);
+        }
+
+        HttpCacheEntry entry = satisfyFromCache(target, httRequest);
+        if (entry == null) {
+            return handleCacheMiss(target, httRequest, context, futureCallback);
+        }
+
+        try {
+            return handleCacheHit(target, httRequest, context, entry, futureCallback);
+        } catch (ClientProtocolException e) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.failed(e);
+            return future;
+        } catch (IOException e) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.failed(e);
+            return future;
+        }
+    }
+
+    private Future<HttpResponse> handleCacheHit(HttpHost target, HttpRequest request,
+            HttpContext context, HttpCacheEntry entry,
+            FutureCallback<HttpResponse> futureCallback)
+            throws ClientProtocolException, IOException {
+        recordCacheHit(target, request);
+
+        Date now = getCurrentDate();
+        if (this.suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.completed(generateCachedResponse(request, context, entry, now));
+            return future;
+        }
+
+        if (!mayCallBackend(request)) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.completed(generateGatewayTimeout(context));
+            return future;
+        }
+
+        if (this.validityPolicy.isRevalidatable(entry)) {
+            return revalidateCacheEntry(target, request, context, entry, now, futureCallback);
+        }
+        return callBackend(target, request, context, futureCallback);
+    }
+
+    private Future<HttpResponse> revalidateCacheEntry(HttpHost target,
+            final HttpRequest request, final HttpContext context, final HttpCacheEntry entry,
+            final Date now, final FutureCallback<HttpResponse> futureCallback) throws ClientProtocolException {
+        this.log.debug("Revalidating the cache entry");
+
+        try {
+            if (this.asynchAsyncRevalidator != null
+                && !staleResponseNotAllowed(request, entry, now)
+                && this.validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
+                final HttpResponse resp = this.responseGenerator.generateResponse(entry);
+                resp.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+
+                this.asynchAsyncRevalidator.revalidateCacheEntry(target, request, context, entry);
+
+                BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+                future.completed(resp);
+                return future;
+            }
+            return revalidateCacheEntry(target, request, context, entry, new FutureCallback<HttpResponse> () {
+
+                public void cancelled() {
+                    futureCallback.cancelled();
+                }
+
+                public void completed(HttpResponse httpResponse) {
+                    futureCallback.completed(httpResponse);
+                }
+
+                public void failed(Exception e) {
+                    if(e instanceof IOException) {
+                        futureCallback.completed(handleRevalidationFailure(request, context, entry, now));
+                        return;
+                    }
+                    futureCallback.failed(e);
+                }
+            });
+        } catch (ProtocolException e) {
+            throw new ClientProtocolException(e);
+        }
+    }
+
+    private Future<HttpResponse> handleCacheMiss(HttpHost target, HttpRequest request,
+            HttpContext context, FutureCallback<HttpResponse> futureCallback) {
+        recordCacheMiss(target, request);
+
+        if (!mayCallBackend(request)) {
+            BasicFuture<HttpResponse> future = new BasicFuture<HttpResponse>(futureCallback);
+            future.completed(new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"));
+            return future;
+        }
+
+        Map<String, Variant> variants =
+            getExistingCacheVariants(target, request);
+        if (variants != null && variants.size() > 0) {
+            return negotiateResponseFromVariants(target, request, context, variants, futureCallback);
+        }
+
+        return callBackend(target, request, context, futureCallback);
+    }
+
+    private HttpCacheEntry satisfyFromCache(HttpHost target, HttpRequest request) {
+        HttpCacheEntry entry = null;
+        try {
+            entry = this.responseCache.getCacheEntry(target, request);
+        } catch (IOException ioe) {
+            this.log.warn("Unable to retrieve entries from cache", ioe);
+        }
+        return entry;
+    }
+
+    private HttpResponse getFatallyNoncompliantResponse(HttpRequest request,
+            HttpContext context) {
+        HttpResponse fatalErrorResponse = null;
+        List<RequestProtocolError> fatalError = this.requestCompliance.requestIsFatallyNonCompliant(request);
+
+        for (RequestProtocolError error : fatalError) {
+            setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            fatalErrorResponse = this.requestCompliance.getErrorForRequest(error);
+        }
+        return fatalErrorResponse;
+    }
+
+    private Map<String, Variant> getExistingCacheVariants(HttpHost target,
+            HttpRequest request) {
+        Map<String,Variant> variants = null;
+        try {
+            variants = this.responseCache.getVariantCacheEntriesWithEtags(target, request);
+        } catch (IOException ioe) {
+            this.log.warn("Unable to retrieve variant entries from cache", ioe);
+        }
+        return variants;
+    }
+
+    private void recordCacheMiss(HttpHost target, HttpRequest request) {
+        this.cacheMisses.getAndIncrement();
+        if (this.log.isDebugEnabled()) {
+            RequestLine rl = request.getRequestLine();
+            this.log.debug("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
+        }
+    }
+
+    private void recordCacheHit(HttpHost target, HttpRequest request) {
+        this.cacheHits.getAndIncrement();
+        if (this.log.isDebugEnabled()) {
+            RequestLine rl = request.getRequestLine();
+            this.log.debug("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
+        }
+    }
+
+    private void recordCacheUpdate(HttpContext context) {
+        this.cacheUpdates.getAndIncrement();
+        setResponseStatus(context, CacheResponseStatus.VALIDATED);
+    }
+
+    private void flushEntriesInvalidatedByRequest(HttpHost target,
+            HttpRequest request) {
+        try {
+            this.responseCache.flushInvalidatedCacheEntriesFor(target, request);
+        } catch (IOException ioe) {
+            this.log.warn("Unable to flush invalidated entries from cache", ioe);
+        }
+    }
+
+    private HttpResponse generateCachedResponse(HttpRequest request,
+            HttpContext context, HttpCacheEntry entry, Date now) {
+        final HttpResponse cachedResponse;
+        if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
+                || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
+            cachedResponse = this.responseGenerator.generateNotModifiedResponse(entry);
+        } else {
+            cachedResponse = this.responseGenerator.generateResponse(entry);
+        }
+        setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+        if (this.validityPolicy.getStalenessSecs(entry, now) > 0L) {
+            cachedResponse.addHeader("Warning","110 localhost \"Response is stale\"");
+        }
+        return cachedResponse;
+    }
+
+    private HttpResponse handleRevalidationFailure(HttpRequest request,
+            HttpContext context, HttpCacheEntry entry, Date now) {
+        if (staleResponseNotAllowed(request, entry, now)) {
+            return generateGatewayTimeout(context);
+        }
+        return unvalidatedCacheHit(context, entry);
+    }
+
+    private HttpResponse generateGatewayTimeout(HttpContext context) {
+        setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+        return new BasicHttpResponse(HttpVersion.HTTP_1_1,
+                HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
+    }
+
+    private HttpResponse unvalidatedCacheHit(HttpContext context,
+            HttpCacheEntry entry) {
+        final HttpResponse cachedResponse = this.responseGenerator.generateResponse(entry);
+        setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+        cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
+        return cachedResponse;
+    }
+
+    private boolean staleResponseNotAllowed(HttpRequest request,
+            HttpCacheEntry entry, Date now) {
+        return this.validityPolicy.mustRevalidate(entry)
+            || (isSharedCache() && this.validityPolicy.proxyRevalidate(entry))
+            || explicitFreshnessRequest(request, entry, now);
+    }
+
+    private boolean mayCallBackend(HttpRequest request) {
+        for (Header h: request.getHeaders("Cache-Control")) {
+            for (HeaderElement elt : h.getElements()) {
+                if ("only-if-cached".equals(elt.getName())) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean explicitFreshnessRequest(HttpRequest request, HttpCacheEntry entry, Date now) {
+        for(Header h : request.getHeaders("Cache-Control")) {
+            for(HeaderElement elt : h.getElements()) {
+                if ("max-stale".equals(elt.getName())) {
+                    try {
+                        int maxstale = Integer.parseInt(elt.getValue());
+                        long age = this.validityPolicy.getCurrentAgeSecs(entry, now);
+                        long lifetime = this.validityPolicy.getFreshnessLifetimeSecs(entry);
+                        if (age - lifetime > maxstale) return true;
+                    } catch (NumberFormatException nfe) {
+                        return true;
+                    }
+                } else if ("min-fresh".equals(elt.getName())
+                            || "max-age".equals(elt.getName())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private String generateViaHeader(HttpMessage msg) {
+        final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
+        final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
+        final ProtocolVersion pv = msg.getProtocolVersion();
+        if ("http".equalsIgnoreCase(pv.getProtocol())) {
+            return String.format("%d.%d localhost (Apache-HttpClient/%s (cache))",
+                new Integer(pv.getMajor()), new Integer(pv.getMinor()), release);
+        }
+        return String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))",
+                pv.getProtocol(), new Integer(pv.getMajor()), new Integer(pv.getMinor()), release);
+    }
+
+    private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
+        if (context != null) {
+            context.setAttribute(CACHE_RESPONSE_STATUS, value);
+        }
+    }
+
+    /**
+     * Reports whether this {@code CachingHttpClient} implementation
+     * supports byte-range requests as specified by the {@code Range}
+     * and {@code Content-Range} headers.
+     * @return {@code true} if byte-range requests are supported
+     */
+    public boolean supportsRangeAndContentRangeHeaders() {
+        return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
+    }
+
+    /**
+     * Reports whether this {@code CachingHttpClient} is configured as
+     * a shared (public) or non-shared (private) cache. See {@link
+     * CacheConfig#setSharedCache(boolean)}.
+     * @return {@code true} if we are behaving as a shared (public)
+     *   cache
+     */
+    public boolean isSharedCache() {
+        return this.sharedCache;
+    }
+
+    Date getCurrentDate() {
+        return new Date();
+    }
+
+    boolean clientRequestsOurOptions(HttpRequest request) {
+        RequestLine line = request.getRequestLine();
+
+        if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod()))
+            return false;
+
+        if (!"*".equals(line.getUri()))
+            return false;
+
+        if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue()))
+            return false;
+
+        return true;
+    }
+
+    Future<HttpResponse> callBackend(final HttpHost target, final HttpRequest request, final HttpContext context, final FutureCallback<HttpResponse> futureCallback) {
+        final Date requestDate = getCurrentDate();
+        this.log.debug("Calling the backend");
+        return this.backend.execute(target, request, context, new FutureCallback<HttpResponse>() {
+
+            public void cancelled() {
+                futureCallback.cancelled();
+            }
+
+            public void completed(HttpResponse httpResponse) {
+                httpResponse.addHeader("Via", generateViaHeader(httpResponse));
+                try {
+                    HttpResponse backendResponse = handleBackendResponse(target, request, requestDate, getCurrentDate(), httpResponse);
+                    futureCallback.completed(backendResponse);
+                } catch (IOException e) {
+                    futureCallback.failed(e);
+                    return;
+                }
+
+            }
+
+            public void failed(Exception e) {
+                futureCallback.failed(e);
+            }
+
+        });
+    }
+
+    private boolean revalidationResponseIsTooOld(HttpResponse backendResponse,
+            HttpCacheEntry cacheEntry) {
+        final Header entryDateHeader = cacheEntry.getFirstHeader("Date");
+        final Header responseDateHeader = backendResponse.getFirstHeader("Date");
+        if (entryDateHeader != null && responseDateHeader != null) {
+            try {
+                Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+                Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
+                if (respDate.before(entryDate)) return true;
+            } catch (DateParseException e) {
+                // either backend response or cached entry did not have a valid
+                // Date header, so we can't tell if they are out of order
+                // according to the origin clock; thus we can skip the
+                // unconditional retry recommended in 13.2.6 of RFC 2616.
+            }
+        }
+        return false;
+    }
+
+    Future<HttpResponse> negotiateResponseFromVariants(final HttpHost target,
+            final HttpRequest request, final HttpContext context,
+            final Map<String, Variant> variants,
+            final FutureCallback<HttpResponse> futureCallback) {
+        final HttpRequest conditionalRequest = this.conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
+
+        final Date requestDate = getCurrentDate();
+        //HttpResponse backendResponse =
+        return this.backend.execute(target, conditionalRequest, new FutureCallback<HttpResponse> () {
+
+            public void cancelled() {
+                futureCallback.cancelled();
+            }
+
+            public void completed(HttpResponse httpResponse) {
+                Date responseDate = getCurrentDate();
+
+                httpResponse.addHeader("Via", generateViaHeader(httpResponse));
+
+                if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
+                    try {
+                        HttpResponse backendResponse = handleBackendResponse(target, request, requestDate, responseDate, httpResponse);
+                        futureCallback.completed(backendResponse);
+                        return;
+                    } catch (IOException e) {
+                        futureCallback.failed(e);
+                        return;
+                    }
+                }
+
+                Header resultEtagHeader = httpResponse.getFirstHeader(HeaderConstants.ETAG);
+                if (resultEtagHeader == null) {
+                    CachingHttpAsyncClient.this.log.warn("304 response did not contain ETag");
+                    callBackend(target, request, context, new FutureCallback<HttpResponse>() {
+
+                        public void cancelled() {
+                            futureCallback.cancelled();
+                        }
+
+                        public void completed(HttpResponse innerHttpResponse) {
+                            futureCallback.completed(innerHttpResponse);
+                        }
+
+                        public void failed(Exception e) {
+                            futureCallback.failed(e);
+                        }
+
+                    });
+                    return;
+                }
+
+                String resultEtag = resultEtagHeader.getValue();
+                Variant matchingVariant = variants.get(resultEtag);
+                if (matchingVariant == null) {
+                    CachingHttpAsyncClient.this.log.debug("304 response did not contain ETag matching one sent in If-None-Match");
+                    callBackend(target, request, context, new FutureCallback<HttpResponse>() {
+
+                        public void cancelled() {
+                            futureCallback.cancelled();
+                        }
+
+                        public void completed(HttpResponse innerHttpResponse) {
+                            futureCallback.completed(innerHttpResponse);
+                        }
+
+                        public void failed(Exception e) {
+                            futureCallback.failed(e);
+                        }
+
+                    });
+                    return;
+                }
+
+                HttpCacheEntry matchedEntry = matchingVariant.getEntry();
+
+                if (revalidationResponseIsTooOld(httpResponse, matchedEntry)) {
+                    retryRequestUnconditionally(target, request, context, matchedEntry, futureCallback);
+                    return;
+                }
+
+                recordCacheUpdate(context);
+
+                HttpCacheEntry responseEntry = getUpdatedVariantEntry(target,
+                        conditionalRequest, requestDate, responseDate, httpResponse,
+                        matchingVariant, matchedEntry);
+
+                HttpResponse resp = CachingHttpAsyncClient.this.responseGenerator.generateResponse(responseEntry);
+                tryToUpdateVariantMap(target, request, matchingVariant);
+
+                if (shouldSendNotModifiedResponse(request, responseEntry)) {
+                    HttpResponse backendResponse = CachingHttpAsyncClient.this.responseGenerator.generateNotModifiedResponse(responseEntry);
+                    futureCallback.completed(backendResponse);
+                    return;
+                }
+
+                HttpResponse backendResponse = resp;
+                futureCallback.completed(backendResponse);
+                return;
+            }
+
+            public void failed(Exception e) {
+                futureCallback.failed(e);
+            }
+        });
+    }
+
+    private void retryRequestUnconditionally(HttpHost target,
+            HttpRequest request, HttpContext context,
+            HttpCacheEntry matchedEntry, FutureCallback<HttpResponse> futureCallback) {
+        HttpRequest unconditional = this.conditionalRequestBuilder
+            .buildUnconditionalRequest(request, matchedEntry);
+        callBackend(target, unconditional, context, futureCallback);
+    }
+
+    private HttpCacheEntry getUpdatedVariantEntry(HttpHost target,
+            HttpRequest conditionalRequest, Date requestDate,
+            Date responseDate, HttpResponse backendResponse,
+            Variant matchingVariant, HttpCacheEntry matchedEntry) {
+        HttpCacheEntry responseEntry = matchedEntry;
+        try {
+            responseEntry = this.responseCache.updateVariantCacheEntry(target, conditionalRequest,
+                    matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
+        } catch (IOException ioe) {
+            this.log.warn("Could not update cache entry", ioe);
+        }
+        return responseEntry;
+    }
+
+    private void tryToUpdateVariantMap(HttpHost target, HttpRequest request,
+            Variant matchingVariant) {
+        try {
+            this.responseCache.reuseVariantEntryFor(target, request, matchingVariant);
+        } catch (IOException ioe) {
+            this.log.warn("Could not update cache entry to reuse variant", ioe);
+        }
+    }
+
+    private boolean shouldSendNotModifiedResponse(HttpRequest request,
+            HttpCacheEntry responseEntry) {
+        return (this.suitabilityChecker.isConditional(request)
+                && this.suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
+    }
+
+    Future<HttpResponse> revalidateCacheEntry(
+            final HttpHost target,
+            final HttpRequest request,
+            final HttpContext context,
+            final HttpCacheEntry cacheEntry,
+            final FutureCallback<HttpResponse> futureCallback) throws ProtocolException {
+
+        final HttpRequest conditionalRequest = this.conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry);
+        final Date requestDate = getCurrentDate();
+        return this.backend.execute(target, conditionalRequest, context, new FutureCallback<HttpResponse>() {
+
+            public void cancelled() {
+                futureCallback.cancelled();
+            }
+
+            public void completed(HttpResponse httpResponse) {
+                   final Date responseDate = getCurrentDate();
+
+                    if (revalidationResponseIsTooOld(httpResponse, cacheEntry)) {
+                        HttpRequest unconditional = CachingHttpAsyncClient.this.conditionalRequestBuilder.buildUnconditionalRequest(request, cacheEntry);
+                        final Date innerRequestDate = getCurrentDate();
+                        CachingHttpAsyncClient.this.backend.execute(target, unconditional, context, new FutureCallback<HttpResponse>() {
+
+                            public void cancelled() {
+                                futureCallback.cancelled();
+                            }
+
+                            public void completed(HttpResponse innerHttpResponse) {
+                                Date innerResponseDate = getCurrentDate();
+                                revalidateCacheEntryCompleted(target, request,
+                                        context, cacheEntry, futureCallback,
+                                        conditionalRequest, innerRequestDate,
+                                        innerHttpResponse, innerResponseDate);
+                            }
+
+                            public void failed(Exception e) {
+                                futureCallback.failed(e);
+                            }
+
+                        });
+                        return;
+                    }
+                    revalidateCacheEntryCompleted(target, request,
+                            context, cacheEntry, futureCallback,
+                            conditionalRequest, requestDate,
+                            httpResponse, responseDate);
+            }
+
+            public void failed(Exception e) {
+                futureCallback.failed(e);
+            }
+
+        });
+    }
+
+    private void revalidateCacheEntryCompleted(
+            final HttpHost target,
+            final HttpRequest request,
+            final HttpContext context,
+            final HttpCacheEntry cacheEntry,
+            final FutureCallback<HttpResponse> futureCallback,
+            final HttpRequest conditionalRequest,
+            final Date requestDate,
+            HttpResponse httpResponse,
+            Date responseDate) {
+
+        httpResponse.addHeader("Via", generateViaHeader(httpResponse));
+
+        int statusCode = httpResponse.getStatusLine().getStatusCode();
+        if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
+            recordCacheUpdate(context);
+        }
+
+        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
+            HttpCacheEntry updatedEntry = null;
+            try {
+                updatedEntry = CachingHttpAsyncClient.this.responseCache.updateCacheEntry(target, request, cacheEntry,
+                        httpResponse, requestDate, responseDate);
+            } catch (IOException e) {
+                futureCallback.failed(e);
+                return;
+            }
+            if (CachingHttpAsyncClient.this.suitabilityChecker.isConditional(request)
+                    && CachingHttpAsyncClient.this.suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
+                futureCallback.completed(CachingHttpAsyncClient.this.responseGenerator.generateNotModifiedResponse(updatedEntry));
+                return;
+            }
+            futureCallback.completed(CachingHttpAsyncClient.this.responseGenerator.generateResponse(updatedEntry));
+            return;
+        }
+
+        if (staleIfErrorAppliesTo(statusCode)
+            && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
+            && CachingHttpAsyncClient.this.validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
+            final HttpResponse cachedResponse = CachingHttpAsyncClient.this.responseGenerator.generateResponse(cacheEntry);
+            cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+            futureCallback.completed(cachedResponse);
+            return;
+        }
+
+        try {
+            HttpResponse backendResponse = handleBackendResponse(target, conditionalRequest,
+                    requestDate, responseDate, httpResponse);
+                futureCallback.completed(backendResponse);
+        } catch (IOException e) {
+            futureCallback.failed(e);
+            return;
+        }
+    }
+
+    private boolean staleIfErrorAppliesTo(int statusCode) {
+        return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+                || statusCode == HttpStatus.SC_BAD_GATEWAY
+                || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+                || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
+    }
+
+    HttpResponse handleBackendResponse(
+            HttpHost target,
+            HttpRequest request,
+            Date requestDate,
+            Date responseDate,
+            HttpResponse backendResponse) throws IOException {
+
+        this.log.debug("Handling Backend response");
+        this.responseCompliance.ensureProtocolCompliance(request, backendResponse);
+
+        boolean cacheable = this.responseCachingPolicy.isResponseCacheable(request, backendResponse);
+        this.responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
+        if (cacheable &&
+            !alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
+            try {
+                return this.responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate,
+                        responseDate);
+            } catch (IOException ioe) {
+                this.log.warn("Unable to store entries in cache", ioe);
+            }
+        }
+        if (!cacheable) {
+            try {
+                this.responseCache.flushCacheEntriesFor(target, request);
+            } catch (IOException ioe) {
+                this.log.warn("Unable to flush invalid cache entries", ioe);
+            }
+        }
+        return backendResponse;
+    }
+
+    private boolean alreadyHaveNewerCacheEntry(HttpHost target, HttpRequest request,
+            HttpResponse backendResponse) {
+        HttpCacheEntry existing = null;
+        try {
+            existing = this.responseCache.getCacheEntry(target, request);
+        } catch (IOException ioe) {
+            // nop
+        }
+        if (existing == null) return false;
+        Header entryDateHeader = existing.getFirstHeader("Date");
+        if (entryDateHeader == null) return false;
+        Header responseDateHeader = backendResponse.getFirstHeader("Date");
+        if (responseDateHeader == null) return false;
+        try {
+            Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+            Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
+            return responseDate.before(entryDate);
+        } catch (DateParseException e) {
+            //
+        }
+        return false;
+    }
+
+    public IOReactorStatus getStatus() {
+        return this.backend.getStatus();
+    }
+
+    public void shutdown() throws InterruptedException {
+        this.backend.shutdown();
+    }
+
+    public void start() {
+        this.backend.start();
+    }
+
+}

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpasyncclient/trunk/httpasyncclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpAsyncClient.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpasyncclient/trunk/pom.xml
URL: http://svn.apache.org/viewvc/httpcomponents/httpasyncclient/trunk/pom.xml?rev=1366958&r1=1366957&r2=1366958&view=diff
==============================================================================
--- httpcomponents/httpasyncclient/trunk/pom.xml (original)
+++ httpcomponents/httpasyncclient/trunk/pom.xml Sun Jul 29 21:47:02 2012
@@ -98,6 +98,11 @@
         <version>${httpclient.version}</version>
       </dependency>
       <dependency>
+        <groupId>org.apache.httpcomponents</groupId>
+        <artifactId>httpclient-cache</artifactId>
+        <version>${httpclient.version}</version>
+      </dependency>
+      <dependency>
         <groupId>commons-logging</groupId>
         <artifactId>commons-logging</artifactId>
         <version>${commons-logging.version}</version>