/* * Copyright 2000-2001,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; import java.io.File; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.jetspeed.cache.FileCacheEntry; import org.apache.jetspeed.cache.FileCacheEventListener; import org.apache.jetspeed.services.logging.JetspeedLogFactoryService; import org.apache.jetspeed.services.logging.JetspeedLogger; /** * FileCache keeps a cache of files up-to-date with a most simple eviction * policy. The eviction policy will keep n items in the cache, and then start * evicting the items ordered-by least used first. The cache runs a thread to * check for both evictions and refreshes. * * @author David S. Taylor <a href="mailto:taylor@apache.org">David Sean Taylor</a> * @version $Id: FileCache.java,v 1.5 2004/03/25 16:27:41 jford Exp $ */ public class FileCache implements java.util.Comparator { protected long scanRate = 300; // every 5 minutes protected int maxSize = 100; // maximum of 100 items protected List listeners = new LinkedList(); private FileCacheScanner scanner = null; private Map cache = null; /** * Static initialization of the logger for this class */ private static final JetspeedLogger logger = JetspeedLogFactoryService .getLogger(FileCache.class.getName()); /** * Default constructor. Use default values for scanReate and maxSize * */ public FileCache() { cache = new HashMap(); this.scanner = new FileCacheScanner(); this.scanner.setDaemon(true); } /** * Set scanRate and maxSize * * @param scanRate * how often in seconds to refresh and evict from the cache * @param maxSize * the maximum allowed size of the cache before eviction starts */ public FileCache(long scanRate, int maxSize) { cache = new HashMap(); this.scanRate = scanRate; this.maxSize = maxSize; this.scanner = new FileCacheScanner(); this.scanner.setDaemon(true); } /** * Set all parameters on the cache * * @param initialCapacity * the initial size of the cache as passed to HashMap * @param loadFactor * how full the hash table is allowed to get before increasing * @param scanRate * how often in seconds to refresh and evict from the cache * @param maxSize * the maximum allowed size of the cache before eviction starts */ public FileCache(int initialCapacity, int loadFactor, long scanRate, int maxSize) { cache = new HashMap(initialCapacity, loadFactor); this.scanRate = scanRate; this.maxSize = maxSize; this.scanner = new FileCacheScanner(); this.scanner.setDaemon(true); } /** * Set the new refresh scan rate on managed files. * * @param scanRate * the new scan rate in seconds */ public void setScanRate(long scanRate) { this.scanRate = scanRate; } /** * Get the refresh scan rate * * @return the current refresh scan rate in seconds */ public long getScanRate() { return scanRate; } /** * Set the new maximum size of the cache * * @param maxSize * the maximum size of the cache */ public void setMaxSize(int maxSize) { this.maxSize = maxSize; } /** * Get the maximum size of the cache * * @return the current maximum size of the cache */ public int getMaxSize() { return maxSize; } /** * Gets an entry from the cache given a key * * @param key * the key to look up the entry by * @return the entry */ public FileCacheEntry get(String key) { return (FileCacheEntry) cache.get(key); } /** * Gets an entry from the cache given a key * * @param key * the key to look up the entry by * @return the entry */ public Object getDocument(String key) { FileCacheEntry entry = (FileCacheEntry) cache.get(key); if (entry != null) { return entry.getDocument(); } return null; } /** * Puts a file entry in the file cache * * @param file * The file to be put in the cache * @param document * the cached document */ public void put(File file, Object document) throws java.io.IOException { FileCacheEntry entry = new FileCacheEntry(file, document); cache.put(file.getCanonicalPath(), entry); } /** * Puts a file entry in the file cache * * @param path * the full path name of the file * @param document * the cached document */ public void put(String key, Object document) throws java.io.IOException { File file = new File(key); FileCacheEntry entry = new FileCacheEntry(file, document); cache.put(file.getCanonicalPath(), entry); } /** * Removes a file entry from the file cache * * @param key * the full path name of the file * @return the entry removed */ public Object remove(String key) { return cache.remove(key); } /** * Add a File Cache Event Listener * * @param listener * the event listener */ public void addListener(FileCacheEventListener listener) { listeners.add(listener); } /** * Start the file Scanner running at the current scan rate. * */ public void startFileScanner() { try { this.scanner.start(); } catch (java.lang.IllegalThreadStateException e) { logger.error("Exception starting scanner", e); } } /** * Stop the file Scanner * */ public void stopFileScanner() { this.scanner.setStopping(true); } /** * Evicts entries based on last accessed time stamp * */ protected void evict() { synchronized (cache) { if (this.getMaxSize() >= cache.size()) { return; } List list = new LinkedList(cache.values()); Collections.sort(list, this); int count = 0; int limit = cache.size() - this.getMaxSize(); for (Iterator it = list.iterator(); it.hasNext();) { if (count >= limit) { break; } FileCacheEntry entry = (FileCacheEntry) it.next(); String key = null; try { key = entry.getFile().getCanonicalPath(); } catch (java.io.IOException e) { logger.error("Exception getting file path: ", e); } // notify that eviction will soon take place for (Iterator lit = this.listeners.iterator(); lit.hasNext();) { FileCacheEventListener listener = (FileCacheEventListener) lit.next(); listener.evict(entry); } cache.remove(key); count++; } } } /** * Comparator function for sorting by last accessed during eviction * */ public int compare(Object o1, Object o2) { FileCacheEntry e1 = (FileCacheEntry) o1; FileCacheEntry e2 = (FileCacheEntry) o2; if (e1.getLastAccessed() < e2.getLastAccessed()) { return -1; } else if (e1.getLastAccessed() == e2.getLastAccessed()) { return 0; } return 1; } /** * inner class that runs as a thread to scan the cache for updates or * evictions * */ protected class FileCacheScanner extends Thread { private boolean stopping = false; public void setStopping(boolean flag) { this.stopping = flag; } /** * Run the file scanner thread * */ public void run() { boolean done = false; try { while (!done) { try { int count = 0; synchronized (FileCache.this) { for (Iterator it = FileCache.this.cache.values().iterator(); it .hasNext();) { FileCacheEntry entry = (FileCacheEntry) it.next(); Date modified = new Date(entry.getFile().lastModified()); if (modified.after(entry.getLastModified())) { for (Iterator lit = FileCache.this.listeners.iterator(); lit .hasNext();) { FileCacheEventListener listener = (FileCacheEventListener) lit .next(); listener.refresh(entry); entry.setLastModified(modified); } } count++; } } if (count > FileCache.this.getMaxSize()) { FileCache.this.evict(); } } catch (Exception e) { logger.error("FileCache Scanner: Error in iteration...", e); } sleep(FileCache.this.getScanRate() * 1000); if (this.stopping) { this.stopping = false; done = true; } } } catch (InterruptedException e) { logger.error("FileCacheScanner: recieved interruption, exiting.", e); } } } // end inner class: FileCacheScanner /** * get an iterator over the cache values * * @return iterator over the cache values */ public Iterator getIterator() { Map tmp = new HashMap(); tmp.putAll(cache); return tmp.values().iterator(); } /** * get the size of the cache * * @return the size of the cache */ public int getSize() { return cache.size(); } }