/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.cache.impl;
import ch.entwine.weblounge.common.request.CacheHandle;
import ch.entwine.weblounge.common.request.CacheTag;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* This class implements an entry into the cache.
*/
public final class CacheEntry implements Serializable {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(CacheEntry.class);
/** Serial version uid */
private static final long serialVersionUID = 5694887351734158681L;
/** The key for this cache entry */
private String key = null;
/** The content buffer */
private byte[] content;
/** The content encoding */
private String encoding = null;
/** The response metadata */
private CacheableHttpServletResponseHeaders headers = null;
/** Date when the entry was added to the cache */
private long creationDate = 0L;
/** Date where the entry's contents were last modified */
private long modificationDate = 0L;
/** Time in ms for the client to revalidate */
private long clientRevalidationTime = 0L;
/** The etag */
private String eTag = null;
/**
* Creates a new cache entry for the given handle, content and metadata.
* <p>
* Note that some cache information, such as the etag and the last
* modification dates will be gathered from the response headers if available.
*
* @param handle
* the cache handle
* @param content
* the content
* @param encoding
* the content encoding
* @param headers
* the metadata
* @throws IllegalArgumentException
* if the content or the headers collection is <code>null</code>
*/
protected CacheEntry(CacheHandle handle, byte[] content, String encoding,
CacheableHttpServletResponseHeaders headers) {
if (handle == null)
throw new IllegalArgumentException("Handle cannot be null");
if (content == null)
throw new IllegalArgumentException("Content cannot be null");
if (headers == null)
throw new IllegalArgumentException("Headers cannot be null");
this.key = handle.getKey();
this.encoding = encoding;
this.content = content;
this.creationDate = handle.getCreationDate();
this.modificationDate = getTimeWithoutMilliseconds(handle.getModificationDate());
this.clientRevalidationTime = handle.getClientRevalidationTime();
this.eTag = createETag(modificationDate);
setHeaders(headers);
}
/**
* Returns the key for this entry.
*
* @return the key
*/
public String getKey() {
return key;
}
/**
* Returns the content encoding.
*
* @return the encoding
*/
public String getEncoding() {
return encoding;
}
/**
* Returns this entry's etag value.
*
* @return the etag
*/
public String getETag() {
return eTag;
}
/**
* Returns the date when this entry was created.
*
* @return the creation date
*/
public long getCreationDate() {
return creationDate;
}
/**
* Returns the date when this entry's content was last modified.
*
* @return the modification date
*/
public long getModificationDate() {
return modificationDate;
}
/**
* Returns the time in ms until the client needs to revalidate the response.
*
* @return the client revalidation time
*/
public long getClientRevalidationTime() {
return clientRevalidationTime;
}
/**
* Returns <code>true</code> if the entry is tagged with <code>tag</code>.
*
* @param tag
* the tag
* @return <code>true</code> if the entry is tagged
*/
public boolean containsTag(CacheTag tag) {
String keyPart = tag.getName() + "=" + tag.getValue();
return key.contains(keyPart);
}
/**
* Sets the response headers, which will be used to extract certain common
* information such as the etag or the last-modified date.
*
* @param headers
* the response headers
*/
public void setHeaders(CacheableHttpServletResponseHeaders headers) {
this.headers = headers;
// Overwrite local Last-Modified?
if (headers.containsHeader("Last-Modified")) {
DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
String lastModifiedHeader = (String) headers.getHeaders().get("Last-Modified");
try {
modificationDate = getTimeWithoutMilliseconds(new Date(Math.max(modificationDate, df.parse(lastModifiedHeader).getTime())));
logger.trace("Changing modification date to '{}' as provided by response headers", lastModifiedHeader);
eTag = createETag(modificationDate);
logger.trace("Changing eTag to '{}' as provided by response headers", eTag);
} catch (ParseException e) {
logger.error("Unexpected date format for 'Last-Modified' header: {}", lastModifiedHeader);
}
}
// Overwrite local ETag?
if (headers.containsHeader("ETag")) {
eTag = (String) headers.getHeaders().get("ETag");
logger.trace("Changing eTag to '{}' as provided by response headers", eTag);
}
}
/**
* Returns the cached response headers.
*
* @return the headers
*/
public CacheableHttpServletResponseHeaders getHeaders() {
return headers;
}
/**
* Returns the cached content.
*
* @return the content
*/
public byte[] getContent() {
return content;
}
/**
* Returns the content type.
*
* @return the content type
*/
public String getContentType() {
Object contentType = headers.getHeaders().get("Content-Type");
if (contentType == null)
return null;
if (contentType instanceof String)
return (String) contentType;
throw new IllegalStateException("Response contained more than one 'Content-type' header");
}
/**
* Returns <code>true</code> if date is not older than the creation date of
* this cache entry.
*
* @param date
* the date
* @return <code>true</code> if this entry is older or equally old
*/
public boolean notModified(long date) {
return date >= modificationDate;
}
/**
* Returns <code>true</code> if <code>eTag</code> is either blank (not
* specified) or if it matches this entry's etag.
*
* @param eTag
* the etag
* @return <code>true</code> if the etag is either empty or matches
*/
public boolean matches(String eTag) {
return StringUtils.isNotBlank(eTag) && this.eTag.equals(eTag);
}
/**
* Returns the etag (including the extra pair of quotes) for the given value,
* which will usually be the creation time of the cached entry.
*
* @param value
* the value
* @return the etag
*/
public static String createETag(long value) {
return "\"WL-" + Long.toHexString(value) + "\"";
}
/**
* Strips the milliseconds from the date and returns it.
*
* @param date
* the data
* @return the date without the milliseconds
*/
private long getTimeWithoutMilliseconds(Date date) {
long millis = date.getTime() / 1000;
millis *= 1000;
return millis;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return key;
}
}