blob: e2a8df732f9d476e1a9440b47be138490b1a4a5b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.internet.cache.internal;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Random;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* The cache holds references to remote resources. The cache can store resources,
* retrieve resources, and delete resources.
*
*/
public class Cache
{
/**
* String instances.
*/
private static final String URI = "uri";
private static final String LOCATION ="location";
private static final String ENTRY = "entry";
private static final String CACHE = "cache";
private static final String LAST_MODIFIED = "lastModified";
private static final String EXPIRATION_TIME = "expirationTime";
private static final String FILE_PROTOCOL = "file:///";
private static final String CACHE_FILE = "cache.xml";
private static final String CACHE_EXTENSION = ".cache";
private static final String TEMP = "temp";
private static final String CACHE_PREFIX = "wtpcache";
private static final String CACHE_SUFFIX = null;
/**
* The default timeout for a cache entry is 1 day.
*/
private static final long TIMEOUT = 86400000;
/**
* The one and only instance of the cache.
*/
private static Cache cacheInstance = null;
/**
* The cache is stored in a hashtable.
*/
private Hashtable cache;
/**
* A set of uncached resources. The cache was not able to cache resources
* in this list. This list allows quickly skipping over these resources in
* future requests.
*/
private Set uncached;
/**
* The location of the cache
*/
private File cacheLocation = null;
/**
* Private constructor.
*/
protected Cache(IPath cacheLocation)
{
this.cacheLocation = cacheLocation.toFile();//Platform.getPluginStateLocation(CachePlugin.getDefault()).toFile();
cache = new Hashtable();
uncached = new HashSet();
}
/**
* Get the one and only instance of the cache.
*
* @return The one and only instance of the cache.
*/
public static Cache getInstance()
{
// if(cacheInstance == null)
// {
// cacheInstance = new Cache(cacheLocation);
// cacheInstance.open(cacheLocation);
// }
return cacheInstance;
}
/**
* Return the local resource for the specified uri. If there is no resource
* the cache will attempt to download and cache the resource before returning.
* If a remote resource cannot be cached this method will return null.
*
* @param uri The URI for which a resource is requested.
* @return The local resource for the specified URI or null if a remote resource cannot be cached.
*/
public String getResource(String uri)
{
if(uri == null) return null;
CacheEntry result = (CacheEntry)cache.get(uri);
// If no result is in the cache and the URI is of an allowed type
// retrieve it and store it in the cache.
if(result == null)
{
if(!uncached.contains(result))
{
result = cacheResource(uri);
}
}
// Retreive a fresh copy of the result if it has timed out.
else if(result.hasExpired())
{
result = refreshCacheEntry(result);
}
if(result == null || result.getLocalFile() == null)
{
return null;
}
return FILE_PROTOCOL + cacheLocation.toString() + "/" + result.getLocalFile();
}
/**
* Get the list of uncached resources.
*
* @return The list of uncached resources.
*/
protected String[] getUncachedURIs()
{
return (String[])uncached.toArray(new String[uncached.size()]);
}
/**
* Clear the list of uncached resources.
*/
protected void clearUncachedURIs()
{
uncached.clear();
}
/**
* Add an uncached resource to the list.
*/
protected void addUncachedURI(String uri)
{
uncached.add(uri);
}
/**
* Cache the specified resource. This method creates a local version of the
* remote resource and adds the resource reference to the cache. If the resource
* cannot be accessed it is not added and null is returned.
*
* @param uri The remote URI to cache.
* @return A new CacheEntry representing the cached resource or null if the remote
* resource could not be retrieved.
*/
protected CacheEntry cacheResource(String uri)
{
CacheEntry cacheEntry = null;
URLConnection conn = null;
InputStream is = null;
OutputStream os = null;
try
{
URL url = new URL(uri);
conn = url.openConnection();
conn.connect();
// Determine if this resource can be cached.
if(conn.getUseCaches())
{
is = conn.getInputStream();//url.openStream();
Random rand = new Random();
String fileName = rand.nextInt() + CACHE_EXTENSION;
File file = new File(cacheLocation, fileName);
// If the file already exists we need to change the file name.
while(!file.createNewFile())
{
fileName = rand.nextInt() + CACHE_EXTENSION;
file = new File(cacheLocation,fileName);
}
os = new FileOutputStream(file);
byte[] bytes = new byte[1024];
int bytelength;
while((bytelength = is.read(bytes)) != -1)
{
os.write(bytes, 0, bytelength);
}
long lastModified = conn.getLastModified();
long expiration = conn.getExpiration();
if(expiration == 0)
{
expiration = System.currentTimeMillis() + TIMEOUT;
}
cacheEntry = new CacheEntry(uri, fileName, lastModified, expiration);
cache.put(uri,cacheEntry);
}
}
catch(Throwable t)
{
// Put the entry in the uncached list so the resolution work will not be performed again.
// TODO: Add in a timeout for the non-located uris.
uncached.add(uri);
}
finally
{
if(is != null)
{
try
{
is.close();
}
catch(IOException e)
{
// Do nothing if the stream cannot be closed.
}
}
if(os != null)
{
try
{
os.close();
}
catch(IOException e)
{
// Do nothing if the stream cannot be closed.
}
}
}
return cacheEntry;
}
/**
* Refresh the cache entry if necessary. The cache entry will be refreshed
* if the remote resource is accessible and the last modified time of the
* remote resource is greater than the last modified time of the cached
* resource.
*
* @param cacheEntry The cache entry to refresh.
* @return The refreshed cache entry.
*/
protected CacheEntry refreshCacheEntry(CacheEntry cacheEntry)
{
URLConnection conn = null;
InputStream is = null;
OutputStream os = null;
try
{
URL url = new URL(cacheEntry.getURI());
conn = url.openConnection();
conn.connect();
long lastModified = conn.getLastModified();
if(lastModified > cacheEntry.getLastModified())
{
long expiration = conn.getExpiration();
if(expiration == 0)
{
expiration = System.currentTimeMillis() + TIMEOUT;
}
is = conn.getInputStream();
String localFile = cacheEntry.getLocalFile();
File tempFile = File.createTempFile(CACHE_PREFIX, CACHE_SUFFIX);
tempFile.deleteOnExit();
os = new FileOutputStream(tempFile);
byte[] bytes = new byte[1024];
int bytelength;
while((bytelength = is.read(bytes)) != -1)
{
os.write(bytes, 0, bytelength);
}
is.close();
os.close();
deleteFile(cacheEntry.getURI());
File f = new File(cacheLocation, localFile);
tempFile.renameTo(f);
cacheEntry.setExpiration(expiration);
cacheEntry.setLastModified(lastModified);
}
// The cache entry hasn't changed. Just update the expiration time.
else
{
long expiration = conn.getExpiration();
if(expiration == 0)
{
expiration = System.currentTimeMillis() + TIMEOUT;
}
cacheEntry.setExpiration(expiration);
}
}
catch(Exception e)
{
// Do nothing.
}
finally
{
if(is != null)
{
try
{
is.close();
}
catch(IOException e)
{
// Do nothing if the stream cannot be closed.
}
}
if(os != null)
{
try
{
os.close();
}
catch(IOException e)
{
// Do nothing if the stream cannot be closed.
}
}
}
return cacheEntry;
}
/**
* Get an array of the cached URIs.
*
* @return An array of the cached URIs.
*/
public String[] getCachedURIs()
{
Set keyset = cache.keySet();
return (String[])keyset.toArray(new String[keyset.size()]);
}
/**
* Close the cache. Closing the cache involves serializing the data to an XML file
* in the plugin state location.
*/
protected void close()
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document cachedoc = builder.newDocument();
Element rootelem = cachedoc.createElement(CACHE);
cachedoc.appendChild(rootelem);
Enumeration uris = cache.keys();
while(uris.hasMoreElements())
{
String key = (String)uris.nextElement();
CacheEntry cacheEntry = (CacheEntry)cache.get(key);
if(cacheEntry != null)
{
Element entry = cachedoc.createElement(ENTRY);
entry.setAttribute(URI, key);
entry.setAttribute(LOCATION, cacheEntry.getLocalFile());
entry.setAttribute(EXPIRATION_TIME, String.valueOf(cacheEntry.getExpirationTime()));
entry.setAttribute(LAST_MODIFIED, String.valueOf(cacheEntry.getLastModified()));
rootelem.appendChild(entry);
}
}
// Write the cache entry.
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
Source input = new DOMSource(cachedoc);
IPath stateLocation = Platform.getPluginStateLocation(CachePlugin.getDefault());
Result output = new StreamResult(stateLocation.toString() + "/" + CACHE_FILE);
transformer.transform(input, output);
}catch(Exception e)
{
System.out.println("Unable to store internet cache.");
}
cacheInstance = null;
}
/**
* Open the cache. Opening the cache involves parsing the cache XML file in
* the plugin state location if it can be read.
*/
protected static void open(IPath cacheLocation)
{
cacheInstance = new Cache(cacheLocation);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
IPath stateLocation = cacheLocation;
DocumentBuilder builder = factory.newDocumentBuilder();
Document cachedoc = builder.parse(stateLocation.toString() + "/" + CACHE_FILE);
Element rootelem = cachedoc.getDocumentElement();
NodeList entries = rootelem.getChildNodes();
int numEntries = entries.getLength();
for(int i = 0; i < numEntries; i++)
{
Node entry = entries.item(i);
if(entry instanceof Element)
{
Element e = (Element)entry;
if(e.getNodeName().equals(ENTRY))
{
String uri = e.getAttribute(URI);
String location = e.getAttribute(LOCATION);
String lm = e.getAttribute(LAST_MODIFIED);
String et = e.getAttribute(EXPIRATION_TIME);
long lastModified = -1;
long expirationTime = -1;
try
{
lastModified = Long.parseLong(lm);
}
catch(NumberFormatException nfe)
{
}
try
{
expirationTime = Long.parseLong(et);
}
catch(NumberFormatException nfe)
{
}
if(uri != null && location != null)
{
cacheInstance.cache.put(uri, new CacheEntry(uri, location, lastModified, expirationTime));
}
}
}
}
}
catch(FileNotFoundException e)
{
// If the file doesn't exist in the correct location there is nothing to load. Do nothing.
}
catch(Exception e)
{
System.out.println("Unable to load cache.");
}
}
/**
* Clear all of the entries from the cache. This method also deletes the cache files.
*/
public void clear()
{
Enumeration keys = cache.keys();
while(keys.hasMoreElements())
{
String key = (String)keys.nextElement();
deleteFile(key);
}
cache.clear();
}
/**
* Delete the cache entry specified by the given URI.
* @param uri The URI entry to remove from the catalog.
*/
public void deleteEntry(String uri)
{
if(uri == null) return;
deleteFile(uri);
cache.remove(uri);
}
/**
* Delete the file specified by the URI.
*
* @param uri The URI of the file to delete.
*/
protected void deleteFile(String uri)
{
CacheEntry cacheEntry = (CacheEntry)cache.get(uri);
if(cacheEntry != null)
{
String location = cacheLocation.toString() + "/" + cacheEntry.getLocalFile();
File file = new File(location);
if(!file.delete())
{
System.out.println("Unable to delete file " + location + " from cache.");
}
}
}
}