com.bazaarvoice.dropwizard.caching.CachedResponse.java Source code

Java tutorial

Introduction

Here is the source code for com.bazaarvoice.dropwizard.caching.CachedResponse.java

Source

/*
 * Copyright 2014 Bazaarvoice, Inc.
 *
 * 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 com.bazaarvoice.dropwizard.caching;

import com.google.common.base.Optional;
import com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap;
import com.sun.jersey.core.util.UnmodifiableMultivaluedMap;
import com.sun.jersey.spi.container.ContainerResponse;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.HttpHeaders.CACHE_CONTROL;
import static com.google.common.net.HttpHeaders.DATE;
import static com.google.common.net.HttpHeaders.EXPIRES;

/**
 * Response loaded from the cache.
 */
public class CachedResponse {
    private static final Logger LOG = LoggerFactory.getLogger(CachedResponse.class);

    /**
     * Names of HTTP headers that are automatically excluded from the cached response headers. The set implementation is
     * case-insensitive.
     * <p/>
     * Age is non-cacheable because it must be recalculated each time the response is returned.
     */
    public static final Set<String> NON_CACHEABLE_HEADERS = HttpHeaderUtils.headerNames("Age");

    private transient DateTime _date;
    private transient Optional<CacheControl> _cacheControl;
    private transient Optional<DateTime> _expires;

    private final int _statusCode;
    private final MultivaluedMap<String, String> _responseHeaders;
    private final byte[] _responseContent;

    public CachedResponse(int statusCode, MultivaluedMap<String, String> headers, byte[] content) {
        _statusCode = statusCode;
        _responseHeaders = checkNotNull(headers);
        _responseContent = checkNotNull(content);
    }

    @Override
    public int hashCode() {
        int hash = 234290234;
        hash = (31 * hash) + _statusCode;
        hash = (31 * hash) + hashCode(_responseHeaders);
        hash = (31 * hash) + Arrays.hashCode(_responseContent);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof CachedResponse)) {
            return false;
        } else if (obj == this) {
            return true;
        }

        CachedResponse other = (CachedResponse) obj;
        return _statusCode == other._statusCode && Arrays.equals(_responseContent, other._responseContent)
                && equals(_responseHeaders, other._responseHeaders);
    }

    public static CachedResponse build(int statusCode, MultivaluedMap<String, Object> headers, byte[] content) {
        checkNotNull(headers);
        return new CachedResponse(statusCode, copyHeaders(headers.entrySet()), content);
    }

    public Response.ResponseBuilder response(DateTime now) {
        Response.ResponseBuilder responseBuilder = Response.status(getStatusCode()).entity(getResponseContent())
                .header("Age", HttpHeaderUtils.toAge(getDate(), now));

        for (Map.Entry<String, List<String>> header : getResponseHeaders().entrySet()) {
            for (String headerValue : header.getValue()) {
                responseBuilder.header(header.getKey(), headerValue);
            }
        }

        return responseBuilder;
    }

    private static boolean equals(MultivaluedMap<String, String> headers1,
            MultivaluedMap<String, String> headers2) {
        for (Map.Entry<String, List<String>> h1 : headers1.entrySet()) {
            if (!equals(h1.getValue(), headers2.get(h1.getKey()))) {
                return false;
            }
        }

        for (String key : headers2.keySet()) {
            if (!headers1.containsKey(key)) {
                return false;
            }
        }

        return true;
    }

    private static boolean equals(List<String> l1, List<String> l2) {
        if (l1 == null) {
            return l2 == null;
        } else if (l2 == null) {
            return false;
        } else if (l1.size() != l2.size()) {
            return false;
        }

        for (int i = 0; i < l1.size(); i += 1) {
            if (!Objects.equals(l1.get(i), l2.get(i))) {
                return false;
            }
        }

        return true;
    }

    private static int hashCode(MultivaluedMap<String, String> headers) {
        int hash = 3402034;

        for (Map.Entry<String, List<String>> header : headers.entrySet()) {
            hash = (31 * hash) + header.getKey().toLowerCase().hashCode();

            for (String value : header.getValue()) {
                hash = (31 * hash) + value.hashCode();
            }
        }

        return hash;
    }

    private static MultivaluedMap<String, String> copyHeaders(Iterable<Map.Entry<String, List<Object>>> headers) {
        StringKeyIgnoreCaseMultivaluedMap<String> copy = new StringKeyIgnoreCaseMultivaluedMap<String>();

        for (Map.Entry<String, List<Object>> header : headers) {
            if (!NON_CACHEABLE_HEADERS.contains(header.getKey())) {
                for (Object headerValue : header.getValue()) {
                    copy.add(header.getKey(), ContainerResponse.getHeaderValue(headerValue));
                }
            }
        }

        return new UnmodifiableMultivaluedMap<String, String>(copy);
    }

    /**
     * True if this response has a configured expiration time.
     */
    public boolean hasExpiration() {
        return getExpires().isPresent();
    }

    /**
     * True if the response has an expiration time and that expiration is after the provided instant.
     */
    public boolean isExpired(DateTime now) {
        return hasExpiration() && getExpires().get().isAfter(now);
    }

    /**
     * Get the date the response was generated.
     * <p/>
     * Retrieves the {@link HttpHeaders#DATE} header or current time if no date header is found.
     *
     * @return response date
     */
    public DateTime getDate() {
        if (_date == null) {
            String dateString = _responseHeaders.getFirst(DATE);

            if (dateString != null) {
                try {
                    _date = HttpHeaderUtils.parseDate(dateString);
                } catch (Exception ex) {
                    LOG.debug("Failed to parse date header: value={}", dateString, ex);
                }
            }

            if (_date == null) {
                _date = DateTime.now();
            }
        }

        return _date;
    }

    /**
     * Get the {@link HttpHeaders#CACHE_CONTROL} header, if set.
     *
     * @return cache-control header or absent if cache-control header is not set
     */
    public Optional<CacheControl> getCacheControl() {
        if (_cacheControl == null) {
            List<String> headerValues = _responseHeaders.get(CACHE_CONTROL);

            _cacheControl = Optional.absent();

            if (headerValues != null) {
                try {
                    _cacheControl = Optional.of(CacheControl.valueOf(HttpHeaderUtils.join(headerValues)));
                } catch (Exception ex) {
                    LOG.debug("Failed to parse cache-control header: value='{}'", headerValues, ex);
                }
            }
        }

        return _cacheControl;
    }

    public Optional<DateTime> getExpires() {
        if (_expires == null) {
            CacheControl cacheControl = getCacheControl().orNull();

            _expires = Optional.absent();

            if (cacheControl != null) {
                int maxAge = CacheControlUtils.getSharedCacheMaxAge(cacheControl);

                if (maxAge >= 0) {
                    _expires = Optional.of(getDate().plusSeconds(maxAge));
                } else {
                    String expiresString = _responseHeaders.getFirst(EXPIRES);

                    if (expiresString != null) {
                        try {
                            _expires = Optional.of(HttpHeaderUtils.parseDate(expiresString));
                        } catch (Exception ex) {
                            LOG.debug("Failed to parse expires header: value={}", expiresString, ex);
                        }
                    }
                }
            }
        }

        return _expires;
    }

    public byte[] getResponseContent() {
        return _responseContent;
    }

    /**
     * Immutable map from response header name (case insensitive) to list of header values.
     *
     * @return response headers
     */
    public MultivaluedMap<String, String> getResponseHeaders() {
        return _responseHeaders;
    }

    public int getStatusCode() {
        return _statusCode;
    }
}