/* * 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.oncrpc.nfs; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.NetworkFile; import org.alfresco.jlan.server.filesys.TreeConnection; /** * Network File Cache Class * * <p>Caches the network files that are currently being accessed by the NFS server. * * @author gkspencer */ public class NetworkFileCache { // Default file timeout public static final long DefaultFileTimeout = 5000L; // 5 seconds public static final long ClosedFileTimeout = 30000L; // 30 seconds // Network file cache, key is the file id private Hashtable<Integer, FileEntry> m_fileCache; // File expiry thread private FileExpiry m_expiryThread; // File timeouts private long m_fileIOTmo = DefaultFileTimeout; private long m_fileCloseTmo = ClosedFileTimeout; // Debug enable flag private boolean m_debug = false; /** * File Entry Class */ protected class FileEntry { // Network file and closed flag private NetworkFile m_file; private boolean m_closed; // Disk share connection private TreeConnection m_conn; // File timeout private long m_timeout; // Session that last accessed the file private SrvSession m_sess; /** * Class constructor * * @param file NetworkFile * @param conn TreeConnection * @param sess SrvSession */ public FileEntry ( NetworkFile file, TreeConnection conn, SrvSession sess) { m_file = file; m_conn = conn; m_sess = sess; updateTimeout(); } /** * Return the file timeout * * @return long */ public final long getTimeout() { return m_timeout; } /** * Return the network file * * @return NetworkFile */ public final NetworkFile getFile() { return m_file; } /** * Return the disk share connection * * @return TreeConnection */ public final TreeConnection getConnection() { return m_conn; } /** * Get the session that last accessed the file * * @return SrvSession */ public final SrvSession getSession() { return m_sess; } /** * Update the file timeout */ public final void updateTimeout() { m_timeout = System.currentTimeMillis() + m_fileIOTmo; } /** * Update the file timeout * * @param tmo long */ public final void updateTimeout(long tmo) { m_timeout = tmo; } /** * Set the session that last accessed the file * * @param sess SrvSession */ public final void setSession( SrvSession sess) { m_sess = sess; } /** * Check if the network file has been closed due to no I/O activity */ public final boolean isClosed() { return m_closed; } /** * Close the file */ public final void closeFile() { if ( m_file != null) { try { m_file.closeFile(); m_closed = true; } catch ( IOException ex) { } } } /** * Open the network file */ public final void openFile() { if ( m_file != null) { try { m_file.openFile( false); m_closed = false; } catch ( IOException ex) { } } } }; /** * File Expiry Thread Class */ protected class FileExpiry implements Runnable { // Expiry thread private Thread m_thread; // Shutdown flag private boolean m_shutdown; /** * Class Constructor * * @param name String */ public FileExpiry ( String name) { // Create and start the file expiry thread m_thread = new Thread(this); m_thread.setDaemon(true); m_thread.setName("NFSFileExpiry_" + name); m_thread.start(); } /** * Main thread method */ public void run() { // Loop until shutdown while ( m_shutdown == false) { // Sleep for a while try { Thread.sleep(m_fileIOTmo / 2); } catch (InterruptedException ex) { } // Get the current system time long timeNow = System.currentTimeMillis(); // Check for expired files synchronized ( m_fileCache) { // Enumerate the cache entries Enumeration enm = m_fileCache.keys(); while ( enm.hasMoreElements()) { // Get the current key Integer fileId = (Integer) enm.nextElement(); // Get the file entry and check if it has expired FileEntry fentry = m_fileCache.get(fileId); if ( fentry != null && fentry.getTimeout() < timeNow) { // Get the network file NetworkFile netFile = fentry.getFile(); // Check if the file has an I/O request pending, if so then reset the file expiry time // for the file if ( netFile.hasIOPending()) { // Update the expiry time for the file entry fentry.updateTimeout(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("NFSFileExpiry: I/O pending file=" + fentry.getFile().getFullName() + ", fid=" + fileId); } else { // Check if the network file is closed, if not then close the file to release the file handle // but keep the file entry in the file cache for a while as the file may be re-opened if ( fentry.isClosed() == false) { // Close the network file fentry.closeFile(); // Update the file entry timeout to keep the file in the cache for a while fentry.updateTimeout( System.currentTimeMillis() + m_fileCloseTmo); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("NFSFileExpiry: Closed file=" + fentry.getFile().getFullName() + ", fid=" + fileId + " (cached)"); } else { // File entry has expired, remove it from the cache m_fileCache.remove(fileId); // Close the file via the disk interface try { // Get the disk interface DiskInterface disk = (DiskInterface) fentry.getConnection().getInterface(); // Close the file disk.closeFile( fentry.getSession(), fentry.getConnection(), netFile); } catch (IOException ex) { } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("NFSFileExpiry: Closed file=" + fentry.getFile().getFullName() + ", fid=" + fileId + " (removed)"); } } } } } } } /** * Request the file expiry thread to shutdown */ public final void requestShutdown() { // Set the shutdown flag m_shutdown = true; // Wakeup the thread try { m_thread.interrupt(); } catch (Exception ex) { } // Wait for the expiry thread to complete try { m_thread.join( m_fileIOTmo); } catch (Exception ex) { } } }; /** * Class constructor * * @param name String */ public NetworkFileCache(String name) { // Create the file cache m_fileCache = new Hashtable<Integer, FileEntry>(); // Start the file expiry thread m_expiryThread = new FileExpiry( name); } /** * Determine if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Add a file to the cache * * @param file NetworkFile * @param conn TreeConnection * @param sess SrvSession */ public synchronized final void addFile(NetworkFile file, TreeConnection conn, SrvSession sess) { synchronized ( m_fileCache) { m_fileCache.put(new Integer(file.getFileId()), new FileEntry(file, conn, sess)); } } /** * Remove a file from the cache * * @param id */ public synchronized final void removeFile(int id) { // Create the search key Integer fileId = new Integer(id); synchronized ( m_fileCache) { m_fileCache.remove(fileId); } } /** * Find a file via the file id * * @param id int * @param sess SrvSession * @return NetworkFile */ public synchronized final NetworkFile findFile(int id, SrvSession sess) { // Create the search key Integer fileId = new Integer(id); FileEntry fentry = null; synchronized ( m_fileCache) { fentry = m_fileCache.get(fileId); } // Return the file, or null if not found if ( fentry != null) { // Update the file timeout fentry.updateTimeout(); // Check if the file is open if ( fentry.isClosed()) fentry.openFile(); // Return the file return fentry.getFile(); } // Invalid file id return null; } /** * Return the count of entries in the cache * * @return int */ public final int numberOfEntries() { return m_fileCache.size(); } /** * Close the expiry cache, close and remove all files from the cache and stop the expiry thread. */ public final void closeAllFiles() { // Enumerate the cache entries Enumeration keys = m_fileCache.keys(); while ( keys.hasMoreElements()) { // Get the current key and lookup the matching value Integer key = (Integer) keys.nextElement(); FileEntry entry = m_fileCache.get(key); // Expire the file entry entry.updateTimeout(0L); } // Shutdown the expiry thread, this should close the files m_expiryThread.requestShutdown(); } /** * Enable/disable debug output * * @param ena boolean */ public final void setDebug( boolean ena) { m_debug = ena; } /** * Set the I/O cache timer value * * @param ioTimer long */ public final void setIOTimer(long ioTimer) { m_fileIOTmo = ioTimer; } /** * Set the close file cache timer value * * @param closeTimer long */ public final void setCloseTimer(long closeTimer) { m_fileCloseTmo = closeTimer; } /** * Dump the cache entries to the debug device */ public final void dumpCache() { // Dump the count of entries in the cache Debug.println("NetworkFileCache entries=" + numberOfEntries()); // Enumerate the cache entries Enumeration keys = m_fileCache.keys(); while ( keys.hasMoreElements()) { // Get the current key and lookup the matching value Integer key = (Integer) keys.nextElement(); FileEntry entry = m_fileCache.get(key); // Dump the entry details Debug.println("fid=" + key + ": " + entry); } } }