/** * Copyright (C) 2012-2014 Gist Labs, LLC. (http://gistlabs.com) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.gistlabs.mechanize.cache.inMemory; import java.util.Calendar; import java.util.Date; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpMessage; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.cookie.DateParseException; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHttpResponse; import com.gistlabs.mechanize.cache.api.CacheEntry; import com.gistlabs.mechanize.exceptions.MechanizeExceptionFactory; public class InMemoryCacheEntry implements CacheEntry { private static final Date OLD = new Date(0); @SuppressWarnings("unused") // debug tool final private HttpUriRequest request; final private HttpResponse response; /** * Used when the response doesn't include a Date header... */ final Date date = new Date(); public InMemoryCacheEntry(final HttpUriRequest request, final HttpResponse response) { if (request==null) throw new NullPointerException("request can't be null!"); this.request = request; if (response==null) throw new NullPointerException("response can't be null!"); this.response = response; } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#isCacheable() */ @Override public boolean isCacheable() { boolean supportsConditionals = has("Last-Modified", this.response) || has("ETag", this.response); boolean isCacheable = has("Cache-Control", "s-maxage", this.response) || has("Cache-Control", "max-age", this.response) || has("Expires", this.response); return supportsConditionals || isCacheable; } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#isValid() */ @Override public boolean isValid() { boolean mustNotCache = has("Pragma", "no-cache", this.response) || has("Cache-Control", "no-cache", this.response); if (mustNotCache) return false; Date now = new Date(); Date cacheControl = getCacheControlExpiresDate(); Date expires = getExpiresDate(); return now.before(cacheControl) || now.before(expires); } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#byteCount() */ @Override public long byteCount() { return this.response.getEntity().getContentLength(); } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#getResponse() */ @Override public HttpResponse getResponse() { return this.response; } /** * * @return */ private Date getDate() { String expires = get("Date", this.response); if (expires.equals("")) return date; // will this ever happen? else return parseDate(expires); } private Date getCacheControlExpiresDate() { String maxage = get("Cache-Control", "max-age", this.response); if (maxage.equals("")) return OLD; int seconds = Integer.parseInt(maxage); Calendar cal = Calendar.getInstance(); cal.setTime(getDate()); cal.add(Calendar.SECOND, seconds); return cal.getTime(); } private Date getExpiresDate() { String expires = get("Expires", this.response); try { return DateUtils.parseDate(expires); } catch(Exception e) { // if not present or parsable... just ignore return OLD; } } protected Date parseDate(final String expires) { try { return DateUtils.parseDate(expires); } catch (DateParseException e) { throw MechanizeExceptionFactory.newException(e); } } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#updateCacheValues(org.apache.http.HttpResponse) */ @Override public CacheEntry updateCacheValues(final HttpResponse response) { transferFirstHeader("Date", response, this.response); transferFirstHeader("ETag", response, this.response); transferFirstHeader("Last-Modified", response, this.response); transferFirstHeader("Cache-Control", response, this.response); transferFirstHeader("Expires", response, this.response); return this; } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#prepareConditionalGet(org.apache.http.client.methods.HttpUriRequest) */ @Override public void prepareConditionalGet(final HttpUriRequest newRequest) { transferFirstHeader("ETag", "If-None-Match", this.response, newRequest); transferFirstHeader("Last-Modified", "If-Modified-Since", this.response, newRequest); } /* (non-Javadoc) * @see com.gistlabs.mechanize.cache.InMemoryCacheEntry#head() */ @Override public HttpResponse head() { BasicHttpResponse response = new BasicHttpResponse(this.response.getStatusLine()); Header[] allHeaders = this.response.getAllHeaders(); for (Header allHeader : allHeaders) response.addHeader(allHeader); response.setEntity(new ByteArrayEntity(new byte[]{})); return response; } protected void transferFirstHeader(final String headerName, final HttpMessage origin, final HttpMessage dest) { transferFirstHeader(headerName, headerName, origin, dest); } protected void transferFirstHeader(final String headerName, final String destName, final HttpMessage origin, final HttpMessage dest) { Header header = origin.getFirstHeader(headerName); if (header!=null) dest.setHeader(destName, header.getValue()); } protected boolean has(final String headerName, final HttpMessage message) { return message.getHeaders(headerName).length>0; } protected String get(final String headerName, final HttpMessage message) { Header header = message.getFirstHeader(headerName); if (header!=null) return header.getValue(); else return ""; } protected boolean has(final String headerName, final String headerValueOrElement, final HttpMessage message) { Header[] headers = message.getHeaders(headerName); for (Header header : headers) { if (header.getValue().equals(headerValueOrElement)) return true; HeaderElement[] elements = header.getElements(); for (HeaderElement element : elements) if (element.getName().equals(headerValueOrElement)) return true; } return false; } protected String get(final String headerName, final String elementName, final HttpMessage message) { Header header = message.getFirstHeader(headerName); if (header==null) return ""; HeaderElement[] elements = header.getElements(); for (HeaderElement element : elements) if (element.getName().equals(elementName)) return element.getValue(); return ""; } }