| /******************************************************************************* |
| * Copyright (c) 2001, 2006 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 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(uri)) |
| { |
| 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 and start the |
| * uncached job if not already started. |
| */ |
| protected void addUncachedURI(String uri) |
| { |
| CacheJob.startJob(); |
| 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. |
| addUncachedURI(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.getStateLocation(CachePlugin.getDefault().getBundle()); |
| 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."); |
| } |
| } |
| } |
| } |