// httpCache.java
// -----------------------
// part of YaCy
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/*
Class documentation:
This class has two purposes:
1. provide a object that carries path and header information
that shall be used as objects within a scheduler's stack
2. static methods for a cache control and cache aging
the class shall also be used to do a cache-cleaning and index creation
*/
package de.anomic.http.client;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import net.yacy.kelondro.blob.ArrayStack;
import net.yacy.kelondro.blob.Compressor;
import net.yacy.kelondro.blob.Heap;
import net.yacy.kelondro.blob.MapView;
import net.yacy.kelondro.data.meta.DigestURI;
import net.yacy.kelondro.data.word.Word;
import net.yacy.kelondro.logging.Log;
import net.yacy.kelondro.order.Base64Order;
import de.anomic.http.server.ResponseHeader;
public final class Cache {
private static final String RESPONSE_HEADER_DB_NAME = "responseHeader.heap";
private static final String FILE_DB_NAME = "file.array";
private static MapView responseHeaderDB = null;
private static Compressor fileDB = null;
private static ArrayStack fileDBunbuffered = null;
private static long maxCacheSize = 0l;
private static File cachePath = null;
private static String prefix;
public static final Log log = new Log("HTCACHE");
public static void init(final File htCachePath, String peerSalt, final long CacheSizeMax) {
cachePath = htCachePath;
maxCacheSize = CacheSizeMax;
prefix = peerSalt;
// set/make cache path
if (!htCachePath.exists()) {
htCachePath.mkdirs();
}
// open the response header database
final File dbfile = new File(cachePath, RESPONSE_HEADER_DB_NAME);
Heap blob = null;
try {
blob = new Heap(dbfile, Word.commonHashLength, Base64Order.enhancedCoder, 1024 * 1024);
} catch (final IOException e) {
Log.logException(e);
}
responseHeaderDB = new MapView(blob, 500, '_');
try {
fileDBunbuffered = new ArrayStack(new File(cachePath, FILE_DB_NAME), prefix, 12, Base64Order.enhancedCoder, 1024 * 1024 * 2);
fileDBunbuffered.setMaxSize(maxCacheSize);
fileDB = new Compressor(fileDBunbuffered, 2 * 1024 * 1024);
} catch (IOException e) {
Log.logException(e);
}
}
/**
* This method changes the HTCache size.
* @param the new cache size in bytes
*/
public static void setCacheSize(final long newCacheSize) {
maxCacheSize = newCacheSize;
fileDBunbuffered.setMaxSize(maxCacheSize);
}
/**
* close the databases
*/
public static void close() {
responseHeaderDB.close();
fileDB.close(true);
}
public static void store(DigestURI url, final ResponseHeader responseHeader, byte[] file) throws IOException {
if (responseHeader == null) throw new IOException("Cache.store of url " + url.toString() + " not possible: responseHeader == null");
if (file == null) throw new IOException("Cache.store of url " + url.toString() + " not possible: file == null");
// store the response header into the header database
final HashMap hm = new HashMap();
hm.putAll(responseHeader);
hm.put("@@URL", url.toNormalform(true, false));
try {
responseHeaderDB.put(url.hash(), hm);
} catch (IOException e) {
throw new IOException("Cache.store: cannot write to headerDB: " + e.getMessage());
}
// store the file
try {
fileDB.put(url.hash().getBytes("UTF-8"), file);
} catch (UnsupportedEncodingException e) {
throw new IOException("Cache.store: cannot write to fileDB (1): " + e.getMessage());
} catch (IOException e) {
throw new IOException("Cache.store: cannot write to fileDB (2): " + e.getMessage());
}
if (log.isFine()) log.logFine("stored in cache: " + url.toNormalform(true, false));
}
/**
* check if the responseHeaderDB and the fileDB has an entry for the given url
* @param url the url of the resource
* @return true if the content of the url is in the cache, false otherwise
*/
public static boolean has(final DigestURI url) {
boolean headerExists;
try {
headerExists = responseHeaderDB.has(url.hash());
} catch (IOException e1) {
try {
fileDB.remove(url.hash().getBytes());
} catch (IOException e) {}
return false;
}
boolean fileExists = fileDB.has(url.hash().getBytes());
if (headerExists && fileExists) return true;
if (headerExists) try {
responseHeaderDB.remove(url.hash());
} catch (IOException e) {}
if (fileExists) try {
fileDB.remove(url.hash().getBytes());
} catch (IOException e) {}
return false;
}
/**
* Returns an object containing metadata about a cached resource
* @param url the {@link URL} of the resource
* @return an {@link IResourceInfo info object}
* @throws IllegalAccessException if the {@link SecurityManager} doesn't allow instantiation
* of the info object with the given protocol
* @throws UnsupportedProtocolException if the protocol is not supported and therefore the
* info object couldn't be created
*/
public static ResponseHeader getResponseHeader(final DigestURI url) {
// loading data from database
Map hdb;
try {
hdb = responseHeaderDB.get(url.hash());
} catch (final IOException e) {
return null;
}
if (hdb == null) return null;
return new ResponseHeader(null, hdb);
}
/**
* Returns the content of a cached resource as {@link InputStream}
* @param url the requested resource
* @return the resource content as {@link InputStream}. In no data
* is available or the cached file is not readable, null
* is returned.
* @throws IOException
*/
/*
public static InputStream getContentStream(final DigestURI url) throws IOException {
// load the url as resource from the cache
byte[] b = getContent(url);
if (b == null) return null;
return new ByteArrayInputStream(b);
}
*/
/**
* Returns the content of a cached resource as byte[]
* @param url the requested resource
* @return the resource content as byte[]. In no data
* is available or the cached file is not readable, null
* is returned.
* @throws IOException
*/
public static byte[] getContent(final DigestURI url) throws IOException {
// load the url as resource from the cache
try {
return fileDB.get(url.hash().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.logException(e);
return null;
}
}
/**
* requesting the content length of a resource is discouraged since it may
* be performed by loading of the resource from the cache and then measuring the
* size after decompression of the content. This may use a lot of CPU resources
* and maybe cause also high IO. Please omit usage of this method as much as possible.
* @param url
* @return the size of the cached content
*/
/*
public static long getResourceContentLength(final DigestURI url) {
// first try to get the length from the response header,
// this is less costly than loading the content from its gzipped cache
ResponseHeader responseHeader = getResponseHeader(url);
if (responseHeader != null) {
long length = responseHeader.getContentLength();
if (length > 0) return length;
}
// load the url as resource from the cache (possibly decompress it),
// and get the length from the content array size
try {
return fileDB.length(url.hash().getBytes("UTF-8"));
} catch (IOException e) {
Log.logException(e);
return -1;
}
}
*/
/**
* removed response header and cached content from the database
* @param url
* @throws IOException
*/
public static void delete(DigestURI url) throws IOException {
responseHeaderDB.remove(url.hash());
fileDB.remove(url.hash().getBytes("UTF-8"));
}
}