/*
* 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());
}
}