/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.server.filesys.cache; import java.io.PrintStream; import java.util.Enumeration; import java.util.Hashtable; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.filesys.FileName; import org.alfresco.jlan.server.filesys.FileStatus; /** * File State Cache Class * * <p>Contains a cache of file/directory information for recently accessed files/directories * to reduce the calls made by the core server code to the shared device driver. * * @author gkspencer */ public class FileStateCache implements Runnable { // Initial allocation size for the state cache private static final int INITIAL_SIZE = 500; // Default expire check thread interval private static final long DEFAULT_EXPIRECHECK = 3000; // 60000; // 1 minute // File state factory, used to create FileState objects private FileStateFactoryInterface m_stateFactory = new DefaultFileStateFactory(); // File state cache, keyed by file path private Hashtable<String, FileState> m_stateCache; // Wakeup interval for the expire file state checker thread private long m_expireInterval = DEFAULT_EXPIRECHECK; // File state expiry time in seconds private long m_cacheTimer = 5 * 60000L; // 5 minutes default // File state listener private FileStateListener m_stateListener; // File state expire daemon thread and shutdown flag private Thread m_expireThread; private boolean m_shutdown = false; // Debug enable and output stream private boolean m_debug = false; /** * Class constructor */ public FileStateCache() { m_stateCache = new Hashtable<String, FileState>(INITIAL_SIZE); // Start the expired file state checker thread m_expireThread = new Thread(this); m_expireThread.setDaemon(true); m_expireThread.setName("FileStateExpire"); m_expireThread.start(); } /** * Return the expired file state checker interval, in milliseconds * * @return long */ public final long getCheckInterval() { return m_expireInterval; } /** * Get the file state cache timer, in milliseconds * * @return long */ public final long getCacheTimer() { return m_cacheTimer; } /** * Return the number of states in the cache * * @return int */ public final int numberOfStates() { return m_stateCache.size(); } /** * Set the default file state cache timer, in milliseconds * * @param tmo long */ public final void setCacheTimer(long tmo) { m_cacheTimer = tmo; } /** * Set the expired file state checker interval, in milliseconds * * @param chkIntval long */ public final void setCheckInterval(long chkIntval) { m_expireInterval = chkIntval; } /** * Determine if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Enable/disable debug output * * @param dbg boolean */ public final void setDebug(boolean dbg) { m_debug = dbg; } /** * Shutdown the file state expire thread */ public final void shutdownRequest() { // Check if the expire thread is valid if ( m_expireThread != null) { // Set the shutdown flag m_shutdown = true; // Wakeup the expire thread m_expireThread.interrupt(); } } /** * Add a new file state to the cache * * @param fstate FileState */ public final synchronized void addFileState(FileState fstate) { // Check if the file state already exists in the cache if ( Debug.EnableInfo && hasDebug() && m_stateCache.get(fstate.getPath()) != null) Debug.println("***** addFileState() state=" + fstate.toString() + " - ALREADY IN CACHE *****"); // DEBUG if ( Debug.EnableError && fstate == null) { Debug.println("addFileState() NULL FileState"); return; } // Set the file state timeout and add to the cache fstate.setExpiryTime(System.currentTimeMillis() + getCacheTimer()); m_stateCache.put( fstate.getPath(), fstate); } /** * Find the file state for the specified path * * @param path String * @return FileState */ public final synchronized FileState findFileState(String path) { return m_stateCache.get(FileState.normalizePath(path)); } /** * Find the file state for the specified path, and optionally create a new file state if not found * * @param path String * @param create boolean * @return FileState */ public final synchronized FileState findFileState(String path, boolean create) { // Find the required file state, if it exists FileState state = m_stateCache.get(FileState.normalizePath(path)); // Check if we should create a new file state if ( state == null && create == true) { // Create a new file state state = m_stateFactory.createFileState( path); // Set the file state timeout and add to the cache state.setExpiryTime(System.currentTimeMillis() + getCacheTimer()); m_stateCache.put(state.getPath(), state); } // Return the file state return state; } /** * Update the name that a file state is cached under, and the associated file state * * @param oldName String * @param newName String * @return FileState */ public final synchronized FileState updateFileState(String oldName, String newName) { // Find the current file state FileState state = m_stateCache.remove(FileState.normalizePath(oldName)); // Rename the file state and add it back into the cache using the new name if ( state != null) { state.setPath(newName); addFileState(state); } // Return the updated file state return state; } /** * Enumerate the file state cache * * @return Enumeration<String> */ public final Enumeration<String> enumerate() { return m_stateCache.keys(); } /** * Remove the file state for the specified path * * @param path String * @return FileState */ public final synchronized FileState removeFileState(String path) { // Remove the file state from the cache FileState state = m_stateCache.remove(FileState.normalizePath(path)); // Check if there is a state listener if ( m_stateListener != null && state != null) m_stateListener.fileStateClosed(state); // Return the removed file state return state; } /** * Rename a file state, remove the existing entry, update the path and add the state back into the * cache using the new path. * * @param newPath String * @param state FileState * @param isDir boolean */ public final void renameFileState(String newPath, FileState state, boolean isDir) { // Synchronize the cache update String oldPath = state.getPath(); synchronized ( m_stateCache) { // Remove the existing file state from the cache, using the original name m_stateCache.remove( state.getPath()); // Update the file state path and add it back to the cache using the new name state.setPath( newPath); state.setFileStatus( isDir ? FileStatus.DirectoryExists : FileStatus.FileExists); m_stateCache.put(state.getPath(), state); } // If the path is to a folder we must change the file status of all file states that are using the old // path if ( isDir == true) { // Get the old path and normalize if ( oldPath.endsWith( FileName.DOS_SEPERATOR_STR) == false) oldPath = oldPath + FileName.DOS_SEPERATOR_STR; oldPath = oldPath.toUpperCase(); // Enumerate the file states Enumeration enm = enumerate(); while ( enm.hasMoreElements()) { // Get the current path from the state cache String statePath = (String) enm.nextElement(); // Check if the path is below the renamed path if ( statePath.length() > oldPath.length() && statePath.startsWith( oldPath)) { // Get the associated file state, update and put back into the cache FileState renState = (FileState) m_stateCache.remove( statePath); renState.setFileStatus( FileStatus.NotExist); renState.setFileId( FileState.UnknownFileId); m_stateCache.put(renState.getPath(), renState); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("++ Rename update " + statePath); } } } } /** * Remove all file states from the cache */ public final synchronized void removeAllFileStates() { // Check if there are any items in the cache if ( m_stateCache == null || m_stateCache.size() == 0) return; // Enumerate the file state cache and remove expired file state objects Enumeration<String> enm = m_stateCache.keys(); while ( enm.hasMoreElements()) { // Get the file state FileState state = m_stateCache.get(enm.nextElement()); // Check if there is a state listener if ( m_stateListener != null) m_stateListener.fileStateClosed(state); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("++ Closed: " + state.getPath()); } // Remove all the file states m_stateCache.clear(); } /** * Remove expired file states from the cache * * @return int */ public final int removeExpiredFileStates() { // Check if there are any items in the cache if ( m_stateCache == null || m_stateCache.size() == 0) return 0; // Enumerate the file state cache and remove expired file state objects Enumeration<String> enm = m_stateCache.keys(); long curTime = System.currentTimeMillis(); int expiredCnt = 0; int openCnt = 0; while(enm.hasMoreElements()) { // Get the file state FileState state = m_stateCache.get(enm.nextElement()); if ( state != null && state.hasNoTimeout() == false) { synchronized ( state) { // Check if the file state has expired and there are no open references to the file if ( state.hasExpired(curTime) && state.getOpenCount() == 0) { // Check if there is a state listener if ( m_stateListener == null || m_stateListener.fileStateExpired(state) == true) { // Remove the expired file state m_stateCache.remove(state.getPath()); // DEBUG // if ( m_debug) // Debug.println("++ Expired file state: " + state); // Update the expired count expiredCnt++; } } else if ( state.getOpenCount() > 0) openCnt++; } } } // DEBUG if ( m_debug && openCnt > 0) { Debug.println("++ Open files " + openCnt); Dump( System.out, false); } // Return the count of expired file states that were removed return expiredCnt; } /** * Add a file state listener * * @param l FileStateListener */ public final void addStateListener(FileStateListener l) { m_stateListener = l; } /** * Remove a file state listener * * @param l FileStateListener */ public final void removeStateListener(FileStateListener l) { if ( m_stateListener == l) m_stateListener = null; } /** * Expired file state checker thread */ public void run() { // Loop forever while ( m_shutdown == false) { // Sleep for the required interval try { Thread.sleep(getCheckInterval()); } catch (InterruptedException ex) { if ( m_shutdown == true) continue; } try { // Check for expired file states int cnt = removeExpiredFileStates(); // Debug if ( Debug.EnableInfo && hasDebug() && cnt > 0) Debug.println("++ Expired " + cnt + " file states, cache=" + m_stateCache.size()); } catch (Exception ex) { Debug.println(ex); } } } /** * Set the file state factory class to be used to create new FileState objects * * @param factory FileStateFactoryInterface */ public final void setFileStateFactory( FileStateFactoryInterface factory) { m_stateFactory = factory; } /** * Dump the state cache entries to the specified stream * * @param out PrintStream * @param dumpAttribs boolean */ public final void Dump(PrintStream out, boolean dumpAttribs) { // Dump the file state cache entries to the specified stream if ( m_stateCache.size() > 0) out.println("++ FileStateCache Entries:"); Enumeration<String> enm = m_stateCache.keys(); long curTime = System.currentTimeMillis(); while ( enm.hasMoreElements()) { String fname = enm.nextElement(); FileState state = m_stateCache.get(fname); out.println("++ " + fname + "(" + state.getSecondsToExpire(curTime) + ") : " + state.toString()); // Check if the state attributes should be output if ( dumpAttribs == true) state.DumpAttributes(out); } } }