/* * 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.Charsets; import com.google.common.base.Throwables; import com.sun.jersey.core.util.Base64; import com.sun.jersey.spi.container.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.core.MultivaluedMap; import java.io.ByteArrayInputStream; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.net.HttpHeaders.CACHE_CONTROL; import static com.google.common.net.HttpHeaders.PRAGMA; /** * Cache related context information for a request. */ public class CacheRequestContext { private static final Logger LOG = LoggerFactory.getLogger(CacheRequestContext.class); private final String _requestMethod; private final URI _requestUri; private final MultivaluedMap<String, String> _headers; private final String _requestHash; private transient RequestCacheControl _cacheControl; private transient Boolean _pragmaNoCache; public CacheRequestContext(String requestMethod, URI requestUri, MultivaluedMap<String, String> headers, String requestHash) { _requestMethod = checkNotNull(requestMethod); _requestUri = checkNotNull(requestUri); _headers = checkNotNull(headers); _requestHash = checkNotNull(requestHash); } public static CacheRequestContext build(ContainerRequest request, Set<String> vary, boolean includeBody) { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); for (String header : vary) { List<String> headerValues = request.getRequestHeader(header); if (headerValues != null && headerValues.size() > 0) { digest.update(header.getBytes(Charsets.UTF_8)); digest.update((byte) 0xFD); for (String value : headerValues) { digest.update(value.getBytes(Charsets.UTF_8)); digest.update((byte) 0xFE); } digest.update((byte) 0xFF); } } if (includeBody) { byte[] requestBody = request.getEntity(byte[].class); if (requestBody == null) { requestBody = new byte[0]; } if (requestBody.length > 0) { digest.update("Body".getBytes(Charsets.UTF_8)); digest.update((byte) 0xFD); digest.update(requestBody); digest.update((byte) 0xFF); } request.setEntityInputStream(new ByteArrayInputStream(requestBody)); } String hash = new String(Base64.encode(digest.digest()), Charsets.US_ASCII); return new CacheRequestContext(request.getMethod(), request.getRequestUri(), request.getRequestHeaders(), hash); } catch (NoSuchAlgorithmException ex) { // This error should not occur since SHA-1 must be included with every java distribution throw Throwables.propagate(ex); } } public URI getRequestUri() { return _requestUri; } public String getRequestMethod() { return _requestMethod; } public String getRequestHash() { return _requestHash; } /** * Get the cache control options set for the request. * <p/> * Returns {@link RequestCacheControl#DEFAULT} if no cache-control header is present or the cache-control header is * invalid. * * @return request cache control options */ public RequestCacheControl getCacheControl() { if (_cacheControl == null) { List<String> values = _headers.get(CACHE_CONTROL); _cacheControl = RequestCacheControl.DEFAULT; if (values != null) { String cacheControlHeader = HttpHeaderUtils.join(values); try { _cacheControl = isNullOrEmpty(cacheControlHeader) ? RequestCacheControl.DEFAULT : RequestCacheControl.valueOf(cacheControlHeader); } catch (IllegalArgumentException ex) { LOG.debug("Invalid request cache control header", ex); } } } return _cacheControl; } /** * True if the <code>Pragma: no-cache</code> header is set on the request. * <p/> * This header should be treated as equivalent to <code>Cache-Control: no-cache</code>. * * @return true if the pragma no-cache header is set */ public boolean isPragmaNoCache() { if (_pragmaNoCache == null) { List<String> values = _headers.get(PRAGMA); _pragmaNoCache = false; if (values != null) { for (String value : values) { if ("no-cache".equalsIgnoreCase(value)) { _pragmaNoCache = true; } } } } return _pragmaNoCache; } }