/* * Copyright 2000-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jetspeed.cache.disk; //jetspeed import org.apache.jetspeed.util.URIEncoder; import org.apache.jetspeed.services.logging.JetspeedLogFactoryService; import org.apache.jetspeed.services.logging.JetspeedLogger; import org.apache.jetspeed.services.threadpool.ThreadPool; import org.apache.jetspeed.services.urlmanager.URLManager; import org.apache.jetspeed.services.urlmanager.URLFetcher; import org.apache.jetspeed.services.urlmanager.URLFetcherDownloader; import org.apache.jetspeed.services.resources.JetspeedResources; //standard java stuff import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.Reader; import java.io.OutputStreamWriter; import java.net.MalformedURLException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.servlet.ServletContext; //turbine import org.apache.turbine.services.servlet.TurbineServlet; /** <p>Sample Implementation of the Disk Cache interface.</p> <p>Entries are updated when DiskCacheDaemon runs</p> @see DiskCache @see org.apache.jetspeed.daemon.impl.DiskCacheDaemon @author <A HREF="mailto:burton@apache.org">Kevin A. Burton</A> @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a> @version $Id: JetspeedDiskCache.java,v 1.50 2004/02/23 02:45:29 jford Exp $ */ public class JetspeedDiskCache implements DiskCache { /** The default cache folder */ public static String DEFAULT_CACHE_DIRECTORY = JetspeedResources.getString( JetspeedResources.CACHE_DIRECTORY_KEY, "WEB-INF/cache" ); /** Stores instances of JetspeedDiskCaches */ private static Hashtable instances = new Hashtable(); /** This is the directory used to cache the documents. */ private String directory; /** This is a hashtable with all the entries in this cache. */ private Hashtable entries = new Hashtable(); /** * Static initialization of the logger for this class */ private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedDiskCache.class.getName()); /** Create an instance of the document cache. */ private JetspeedDiskCache(String directory) { this.directory = directory; if ( DEFAULT_CACHE_DIRECTORY.equals("use-servlet-temp") ) { String tempdir = new String("WEB-INF/cache"); try { ServletContext sc = TurbineServlet.getServletContext(); tempdir = sc.getAttribute("javax.servlet.context.tempdir").toString() + "/jetspeed/cache"; if ( logger.isDebugEnabled() ) { logger.debug("DISK CACHE: will create cache in servlet temp directory " + tempdir); } } catch (Exception e) { logger.error("DISK CACHE: problems creating cache in servlet temp directory " + " falling back to WEB-INF/cache : " + e); } this.directory = tempdir; } else { if ( logger.isDebugEnabled() ) { logger.debug("DISK CACHE: will use cache in user configured directory " + directory); } } } /** Create entries in the hashtable corresponding to the cached files... @see DiskCache#getEntries */ private void initEntries() { logger.info("Disk Cache init Entries..."); //HACK: we need the URLManager started now to avoid locks... //init() acts as a barrier, making our thread wait until //URLManager is done initialization. //This is the long time sought sporadic race condition/lock :-) //To whomever rewrite this code: please change API so the cache is initialized //through the URLManager service *after* the Manager is initialized, and possibly //accessed through URLManager.getReader( url ) or something similar. try { org.apache.turbine.services.TurbineServices .getInstance() .getService( org.apache.jetspeed.services.urlmanager.URLManagerService.SERVICE_NAME ).init(); } catch (Throwable t) { logger.error( "initEntries: Unable to start URLManagerService", t ); } File temp = new File( directory ); String files[] = temp.list(); if (files == null) { logger.error("DiskCache.initEntries: Error!!! - The cache directory cannot be found: " + directory); } for ( int i = 0; i < files.length; ++i ) { if ( files[i].indexOf("http_") == 0 ) { logger.info("Initializing cache entry: " + files[i]); JetspeedDiskCacheEntry ent = new JetspeedDiskCacheEntry( new File( getRoot(), files[i] ) ); logger.info("Adding cache entry for " + ent.getSourceURL()); String interned = ent.getSourceURL().intern(); entries.put( interned, ent); URLManager.register( interned, URLManager.STATUS_OK, "Recovered from cache" ); } } logger.info("Disk Cache init Entries DONE."); } /** Get a list of all the documents within the cache... Modified to create the entries in the hashtable... @see DiskCache#getEntries */ public DiskCacheEntry[] getEntries() { Vector diskEntries = new Vector(); Enumeration cacheEntries = entries.elements(); logger.info("Calling JetspeedDiskCache getEntries"); while(cacheEntries.hasMoreElements()) { diskEntries.addElement(cacheEntries.nextElement()); } DiskCacheEntry[] found = new DiskCacheEntry[diskEntries.size()]; diskEntries.copyInto(found); return found; } /** Return the root of this DiskCache @see DiskCache#getRoot */ public String getRoot() { new File( this.directory ).mkdirs(); return this.directory; } /** @see DiskCache#getEntry( String url ) */ public DiskCacheEntry getEntry( String url ) throws IOException { return getEntry( url, false ); } /** Force this URL to update */ public DiskCacheEntry getEntry( String url, boolean force ) throws IOException { if ( url == null ) { throw new IllegalArgumentException("You must specify a URL to obtain an entry from the cache"); } //return right away if the entry exists in the cache... String interned = url.intern(); JetspeedDiskCacheEntry entry = (JetspeedDiskCacheEntry)entries.get(interned); if( entry != null) { //Log.info("Returning local URL because it is cached: " + interned ); if(force) { logger.info("Refreshing local URL!!!" + interned); URLFetcher.refresh(interned); } return entry; } //attempt to see if the user didn't specify a URL if they didn't then //assume it is localhost with the servlet port logger.warn( "Cache getEntry Called with " + url ); if ( DiskCacheUtils.isLocal( url ) ) { String local = DiskCacheUtils.getLocalURL( url ).intern(); JetspeedDiskCacheEntry dce = (JetspeedDiskCacheEntry)entries.get( local ); if(dce == null ) { logger.info("Adding Local to cache list: " + local); dce = new JetspeedDiskCacheEntry( local ); entries.put(local, dce); URLManager.register( local, URLManager.STATUS_OK, "Local added" ); } logger.info("Returning local cached URL"); return dce; } //only return for URLs that are cacheable and that are based on URLs //that are remote if ( DiskCacheUtils.isCacheable( url ) ) { if ( ( DiskCacheUtils.isCached( this, url ) == false ) || force ) { //Log.info( "DiskCache: MISS - fectching document: " + url ); //if it doesn't exist then pull it down from a URL and save it to a file // SGP We can arrive here either because force was true or // because force is false and the url is not cached. // We must force load in both cases this.add( url, true ); } return this.getEntry(url, force); //return new JetspeedDiskCacheEntry( DiskCacheUtils.getFile( this, url ) ); } else { //else it is a remote URL and can not be cached. logger.info( "DiskCache: this URL can't be stored in cache... providing it directly." + url ); return new JetspeedDiskCacheEntry( url ); } } /** Get an Entry given a Reader and the URL from which it has been fetched. @see DiskCache#getEntry( String url, Reader is ) */ public DiskCacheEntry getEntry( String url, Reader is ) throws IOException { String uri = URIEncoder.encode( url ); String oldfilename = this.getRoot() + "/old." + uri; String filename = DiskCacheUtils.getFile( this, url ).getAbsolutePath(); String newfilename = this.getRoot() + "/new." + uri; File file = new File( DiskCacheUtils.getFile( this, url ).getAbsolutePath() ); File newfile = new File( newfilename); OutputStreamWriter os = new OutputStreamWriter (new FileOutputStream( newfile ), "utf-8" ); //now process the InputStream... char chars[] = new char[200]; int readCount = 0; while( ( readCount = is.read( chars )) > 0 ) { os.write(chars, 0, readCount); } is.close(); os.close(); File oldfile = new File( oldfilename); if(oldfile.exists()) oldfile.delete(); if(newfile.exists() && newfile.length() > 0) { file = new File( filename ); file.renameTo(oldfile); newfile.renameTo(file); } try { if( oldfile.exists() ) oldfile.delete(); } catch (Exception e) { logger.info("Exception " + e.getMessage() + " while deleting " + oldfilename, e); } JetspeedDiskCacheEntry dce = (JetspeedDiskCacheEntry) entries.get(url.intern()); if (dce != null ) { dce.setFile( file ); return dce; } else { return this.getEntry(url, false); } } /** @see DiskCache#remove( String url ) */ public void remove( String url ) throws IOException { String uri = URIEncoder.encode( url ); if( DiskCacheUtils.isCached( this, url ) ) { entries.remove(url.intern()); URLManager.unregister( url.intern() ); File file = DiskCacheUtils.getFile( this, url ); if(file.exists()) { file.delete(); } } String oldfilename = this.getRoot() + "/old." + uri; File file = new File(oldfilename); if(file.exists()) { file.delete(); } String newfilename = this.getRoot() + "/new." + uri; file = new File(newfilename); if(file.exists()) { file.delete(); } } /** @see DiskCache#add( String url ) */ public void add( String url ) throws IOException { add( url, false ); } /** @see DiskCache#add( String url ) */ public void add( String url, boolean force ) throws IOException { String interned = url.intern(); this.fetch( url, DiskCacheUtils.getFile( this, url ).getAbsolutePath(), force ); if(entries.get(interned) != null ) return; entries.put(interned, new JetspeedDiskCacheEntry(interned)); URLManager.register( interned, URLManager.STATUS_OK, "Added by Program" ); } /** @see DiskCache#fetch( String url, String cache ) @param url the url to retrieve @param cache what file to store it in. */ public String fetch( String url, String cache ) throws IOException { return fetch( url, cache, false ); } /** Pulls in the remote URL from the net and saves it to disk @see DiskCache#fetch( String url, String cache ) @param url the url to retrieve @param cache what file to store it in. */ public String fetch( String url, String cache, boolean force ) throws IOException { if (url == null) { throw new IllegalArgumentException("url cannot be null"); } if (cache == null) { throw new IllegalArgumentException("cache cannot be null"); } try { //The URL fecther will try to get the URL or it will throw //an Exception here. Reader is = URLFetcher.fetch( url, force ); OutputStreamWriter os = new OutputStreamWriter( new FileOutputStream( cache ), "utf-8" ); //now process the InputStream... char chars[] = new char[200]; int readCount = 0; while( ( readCount = is.read( chars )) > 0 ) { os.write(chars, 0, readCount); } is.close(); os.close(); } catch (MalformedURLException e) { logger.error("Error in URL", e ); } return cache; } /** @see DiskCache#refresh */ public void refresh( String url ) { ThreadPool.process( new URLFetcherDownloader( url ) ); } /** Return the default instance of the JetspeedDiskCache cache. */ public static JetspeedDiskCache getInstance() { return JetspeedDiskCache.getInstance( DEFAULT_CACHE_DIRECTORY ); } /** Return the default instance of the JetspeedDiskCache cache. @param location A directory to store the cache at. */ public static JetspeedDiskCache getInstance( String directory ) { synchronized(JetspeedDiskCache.instances) { JetspeedDiskCache cache = (JetspeedDiskCache)JetspeedDiskCache.instances.get(directory); if (cache == null) { cache = new JetspeedDiskCache(directory); JetspeedDiskCache.instances.put( directory, cache ); logger.info("DISK CACHE: Initing cache for " + directory); cache.initEntries(); logger.info("DISK CACHE: Inited cache:" + directory); } return cache; } } /** */ public boolean isCached(String url) { return entries.containsKey(url.intern()); } }