org.callimachusproject.server.chain.CacheHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.callimachusproject.server.chain.CacheHandler.java

Source

/*
 * Copyright (c) 2013 3 Round Stones Inc., Some Rights Reserved
 *
 * Licensed 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.
 *
 */
package org.callimachusproject.server.chain;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;

import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpAsyncClient;
import org.apache.http.impl.client.cache.ManagedHttpCacheStorage;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
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.IOReactorStatus;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.callimachusproject.server.AsyncExecChain;
import org.callimachusproject.server.helpers.AutoClosingAsyncClient;
import org.callimachusproject.server.helpers.CalliContext;
import org.callimachusproject.server.helpers.ResponseCallback;
import org.callimachusproject.server.util.HTTPDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CacheHandler implements AsyncExecChain {
    private final class DelegatingClient extends CloseableHttpAsyncClient {
        private final AsyncExecChain delegate;
        private boolean running;

        public DelegatingClient(AsyncExecChain delegate) {
            this.delegate = delegate;
        }

        public void start() {
            running = true;
        }

        public void close() {
            shutdown();
        }

        public void shutdown() {
            running = false;
        }

        public boolean isRunning() {
            return running;
        }

        public IOReactorStatus getStatus() {
            return null;
        }

        public HttpParams getParams() {
            return null;
        }

        public ClientAsyncConnectionManager getConnectionManager() {
            return null;
        }

        public <T> Future<T> execute(HttpAsyncRequestProducer arg0, HttpAsyncResponseConsumer<T> arg1,
                HttpContext arg2, FutureCallback<T> arg3) {
            throw new UnsupportedOperationException(
                    "CachingHttpAsyncClient does not caching for streaming HTTP exchanges");
        }

        public Future<HttpResponse> execute(HttpHost target, final HttpRequest request, final HttpContext context,
                FutureCallback<HttpResponse> callback) {
            return delegate.execute(target, request, context, callback);
        }
    }

    private final Logger logger = LoggerFactory.getLogger(CacheHandler.class);
    private final HTTPDateFormat modifiedformat = new HTTPDateFormat();
    private final AsyncExecChain delegate;
    private final ResourceFactory resourceFactory;
    private final CacheConfig config;
    private final Map<HttpHost, HttpAsyncClient> clients = new HashMap<HttpHost, HttpAsyncClient>();

    public CacheHandler(AsyncExecChain delegate, ResourceFactory resourceFactory, CacheConfig config) {
        this.delegate = delegate;
        this.resourceFactory = resourceFactory;
        this.config = config;
    }

    public synchronized void reset() {
        clients.clear();
    }

    @Override
    public Future<HttpResponse> execute(HttpHost target, final HttpRequest request, final HttpContext context,
            FutureCallback<HttpResponse> callback) {
        if (config.isHeuristicCachingEnabled()) {
            return getClient(target).execute(target, request, context, new ResponseCallback(callback) {
                public void completed(HttpResponse result) {
                    setCacheControlIfCacheable(request, result, context);
                    super.completed(result);
                }
            });
        } else {
            return getClient(target).execute(target, request, context, callback);
        }
    }

    private synchronized HttpAsyncClient getClient(HttpHost target) {
        if (clients.containsKey(target))
            return clients.get(target);
        logger.debug("Initializing server side cache for {}", target);
        ManagedHttpCacheStorage storage = new ManagedHttpCacheStorage(config);
        CachingHttpAsyncClient cachingClient = new CachingHttpAsyncClient(new DelegatingClient(delegate),
                resourceFactory, storage, config);
        HttpAsyncClient client = new AutoClosingAsyncClient(cachingClient, storage);
        clients.put(target, client);
        return client;
    }

    /**
     * Adds max-age cache-control based on last-modified heuristic for client
     * caching.
     */
    void setCacheControlIfCacheable(final HttpRequest request, HttpResponse response, final HttpContext context) {
        String method = request.getRequestLine().getMethod();
        int sc = response.getStatusLine().getStatusCode();
        if ("GET".equals(method) || "HEAD".equals(method)) {
            switch (sc) {
            case 200:
            case 203:
            case 206:
            case 300:
            case 301:
            case 302:
            case 303:
            case 307:
            case 308:
            case 410:
                setCacheControl(response, context);
            case 304:
                if (response.getFirstHeader("Cache-Control") != null) {
                    setCacheControl(response, context);
                }
            }
        }
    }

    private void setCacheControl(HttpResponse response, final HttpContext context) {
        long now = CalliContext.adapt(context).getReceivedOn();
        Header lastMod = response.getLastHeader("Last-Modified");
        Header[] headers = response.getHeaders("Cache-Control");
        if (headers != null && headers.length > 0 && now > 0 && lastMod != null) {
            String cc = getCacheControl(headers, lastMod, now);
            if (cc != null) {
                response.removeHeaders("Cache-Control");
                response.setHeader("Cache-Control", cc);
            }
        }
    }

    private String getCacheControl(Header[] cache, Header lastMod, long now) {
        StringBuilder sb = new StringBuilder();
        for (Header hd : cache) {
            if (sb.length() > 0) {
                sb.append(",");
            }
            sb.append(hd.getValue());
        }
        if (sb.length() == 0 || sb.indexOf("max-age") < 0 && sb.indexOf("s-maxage") < 0
                && sb.indexOf("no-cache") < 0 && sb.indexOf("no-store") < 0) {
            int maxage = getMaxAgeHeuristic(lastMod, now);
            if (maxage > 0) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append("max-age=").append(maxage);
                return sb.toString();
            }
        }
        return null;
    }

    private int getMaxAgeHeuristic(Header lastModified, long now) {
        long lm = lastModified(lastModified);
        int fraction = (int) ((now - lm) / 10000);
        return Math.min(fraction, 24 * 60 * 60);
    }

    private long lastModified(Header lastModified) {
        return modifiedformat.parseHeader(lastModified);
    }

}