Java tutorial
/* * 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; } }