/* * 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.db; import java.io.File; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.server.core.DeviceContextException; import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.DiskSharedDevice; import org.alfresco.jlan.server.filesys.FileAttribute; import org.alfresco.jlan.server.filesys.FileSystem; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.cache.FileStateCache; import org.alfresco.jlan.server.filesys.loader.DeleteFileRequest; import org.alfresco.jlan.server.filesys.loader.FileLoader; import org.alfresco.jlan.server.filesys.loader.FileRequestQueue; import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; import org.alfresco.jlan.util.MemorySize; import org.springframework.extensions.config.ConfigElement; /** * Database Device Context Class * * @author gkspencer */ public class DBDeviceContext extends DiskDeviceContext { // Default file state cache timeout public final static long DEFAULT_CACHETIMEOUT = 5 * 60000L; // 5 minutes public final static long MIN_CACHETIMEOUT = 5000L; // 5 seconds public final static long MAX_CACHETIMEOUT = 60 * 60000L; // 1 hour // Default file state cache check interval public final static long DEFAULT_CACHECHECK = 1 * 60000L; // 1 minutes public final static long MIN_CACHECHECK = 2000L; // 2 seconds public final static long MAX_CACHECHECK = 20 * 60000L; // 20 minutes // Milliseconds per day private static final long MillisecondsPerDay = 24L * 60L * 60000L; // Minimum allowed maximum file size private static final long MinimumMaxfileSize = 512 * MemorySize.KILOBYTE; // File state cache timeout, in milliseconds private long m_cacheTimer = DEFAULT_CACHETIMEOUT; private long m_cacheCheckInterval = DEFAULT_CACHECHECK; // NTFS streams enable flag private boolean m_ntfsStreams = true; // Database interface class // // The database interface implementation may also provide the file loader interface. In this case // database interface initialization must set the loader class. private String m_dbifClass; private DBInterface m_dbInterface; // Database interface configuration values private ConfigElement m_dbifConfig; // File loader class, used to load/save file data private String m_loaderClass; private FileLoader m_loader; // Loader class configuration values private ConfigElement m_loaderConfig; // Trash can enable, mark files as deleted rather than actually deleting private boolean m_trashCan; // Retention period, milliseconds to add to current date/time value, or -1 to disable private long m_retentionPeriod = -1; // Required database features private int m_dbFeatures; // Root directory information private DBFileInfo m_rootInfo; // Maximum file size to allow on the filesystem, zero indicates no limit private long m_maxFileSize; // Mark files as offline, optionally above a certain size private boolean m_offlineFiles; private long m_offlineFileSize; // List of files to be deleted when the database is back online private FileRequestQueue m_offlineDeleteList; // File state cache private FileStateCache m_stateCache; // Debug enable private boolean m_debug; /** * Class constructor * * @param args ConfigElement * @exception DeviceContextException */ public DBDeviceContext(ConfigElement args) throws DeviceContextException { super(); // Initialize the database interface initialize(args); } /** * Class constructor * * @param name String * @param args ConfigElement * @exception DeviceContextException */ public DBDeviceContext(String name, ConfigElement args) throws DeviceContextException { super(); // Set the shared device name setShareName( name); // Initialize the database interface initialize(args); } /** * Initialize the database device context * * @param args ConfigElement * @exception DeviceContextException */ protected final void initialize(ConfigElement args) throws DeviceContextException { // Set the filesystem attributes setFilesystemAttributes(FileSystem.CaseSensitiveSearch + FileSystem.CasePreservedNames); // Initialize the database interface ConfigElement dbParams = args.getChild("DatabaseInterface"); if ( dbParams != null) { // Get the database interface class name and create a new instance ConfigElement dbClass = dbParams.getChild("class"); if ( dbClass == null) throw new DeviceContextException("Database interface class not specified"); // Create the database interface try { // Create a database interface instance Object dbObj = Class.forName(dbClass.getValue()).newInstance(); if ( dbObj instanceof DBInterface) { // Set the database interface and parameters m_dbifClass = dbClass.getValue(); m_dbInterface = (DBInterface) dbObj; m_dbifConfig = dbParams; } else throw new DeviceContextException("Database interface class is not an instance of DBInterface"); } catch (Exception ex) { throw new DeviceContextException("Database interface error, " + ex.toString()); } } else throw new DeviceContextException("Invalid Database Interface configuration"); // Get the file loader class name and arguments ConfigElement ldrParams = args.getChild("FileLoader"); if ( ldrParams != null) { // Set the loader configuration values m_loaderConfig = ldrParams; ConfigElement ldrClass = m_loaderConfig.getChild("class"); if ( ldrClass == null || ldrClass.getValue().length() == 0) throw new DeviceContextException("Invalid File Loader configuration"); // Set the file loader class m_loaderClass = ldrClass.getValue(); } else if ( m_dbInterface instanceof FileLoader) { // Database interface is also the file loader m_loader = (FileLoader) m_dbInterface; } // Get the file state cache timer, if specified ConfigElement nameVal = args.getChild("CacheTime"); if ( nameVal != null) { // Convert the cache timeout value String cacheTmo = nameVal.getValue(); if ( cacheTmo != null) { // Validate the cache time value try { // Convert the cache timeout string to a numeric value m_cacheTimer = Long.valueOf(cacheTmo).longValue() * 1000L; // Check that the value is within the valid range if ( m_cacheTimer < MIN_CACHETIMEOUT) m_cacheTimer = MIN_CACHETIMEOUT; else if ( m_cacheTimer > MAX_CACHETIMEOUT) m_cacheTimer = MAX_CACHETIMEOUT; } catch (NumberFormatException ex) { } } } // Get the file state cache check interval, if specified nameVal = args.getChild("CacheCheckInterval"); if ( nameVal != null) { // Convert the cache check interval String cacheCheck = nameVal.getValue(); if ( cacheCheck != null) { // Validate the cache check interval value try { // Convert the cache check interval string to a numeric value m_cacheCheckInterval = Long.valueOf(cacheCheck).longValue() * 1000L; // Check that the value is within the valid range if ( m_cacheCheckInterval < MIN_CACHECHECK) m_cacheCheckInterval = MIN_CACHECHECK; else if ( m_cacheCheckInterval > MAX_CACHECHECK) m_cacheCheckInterval = MAX_CACHECHECK; } catch (NumberFormatException ex) { } } } // Check if debug output is enabled if ( args.getChild("Debug") != null) m_debug = true; // Check if NTFS streams are disabled if ( args.getChild("disableNTFSStreams") != null) m_ntfsStreams = false; // Check if the trash can feature should be enabled if ( args.getChild("enableTrashCan") != null) m_trashCan = true; // Check if files should be marked as offline if ( args.getChild( "offlineFiles") != null) { // Mark files as offline m_offlineFiles = true; } // Check if offline files are enabled above a specified size nameVal = args.getChild( "offlineFileSize"); if ( nameVal != null) { try { m_offlineFileSize = MemorySize.getByteValue( nameVal.getValue()); // Range check the offline file size if ( m_offlineFileSize < 0) throw new DeviceContextException( "Invalid offline files size, " + nameVal.getValue()); // Enable offline files m_offlineFiles = true; } catch ( NumberFormatException ex) { throw new DeviceContextException( "Invalid offline files size, " + nameVal.getValue()); } } // Check if a maximum file size has been specified nameVal = args.getChild( "MaxFileSize"); if ( nameVal != null) { // Validate the maximum file size try { m_maxFileSize = MemorySize.getByteValue( nameVal.getValue()); // Range check the maximum file size if ( m_maxFileSize < MinimumMaxfileSize) throw new DeviceContextException( "Maximum file size is below minimum allowed setting (" + MinimumMaxfileSize/MemorySize.KILOBYTE + "K)"); } catch ( NumberFormatException ex) { throw new DeviceContextException( "Invalid maximum file size value, " + nameVal.getValue()); } } // Check if quota management should be enabled for this filesystem if ( args.getChild("QuotaManagement") != null) { // Check if quota manager debug output is enabled boolean quotaDebug = args.getChild("QuotaDebug") != null ? true : false; // Create the default quota manager setQuotaManager(new DBQuotaManager(this, quotaDebug)); } // Get the retention period in days, if specified nameVal = args.getChild("RetentionPeriod"); if ( nameVal != null) { try { // Convert the retention period in days value long retainDays = Long.parseLong(nameVal.getValue()); // Range check the retention period if ( retainDays < 0 || retainDays > 3650) throw new DeviceContextException("RetentionPeriod out of valid range (0 - 3650)"); // Convert the retention period to a milliseconds interval that can be added to the current date/time m_retentionPeriod = retainDays * MillisecondsPerDay; } catch (NumberFormatException ex) { throw new DeviceContextException("RetentionPeriod is invalid, " + nameVal.getValue()); } } // Check if the database interface supports retention, if retention has been enabled if ( hasRetentionPeriod() && getDBInterface().supportsFeature(DBInterface.FeatureRetention) == false) throw new DeviceContextException("Database interface does not support retention"); // Create the file loader instance and get the database features required by the loader int dbFeatures = 0; if ( m_loaderClass != null) { try { // Create the file loader instance m_loader = (FileLoader) Class.forName(m_loaderClass).newInstance(); // Get the database features that the database interface must implement to support this file loader dbFeatures = m_loader.getRequiredDBFeatures(); } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Rethrow the exception throw new DeviceContextException(ex.getMessage()); } } else if ( m_loader == null) throw new DeviceContextException("File loader not specified"); // Create the root directory information m_rootInfo = new DBFileInfo("\\", "\\", 0, 0); m_rootInfo.setFileAttributes(FileAttribute.Directory); m_rootInfo.setMode( DBDiskDriver.DefaultNFSDirMode); m_rootInfo.setGid( 0); m_rootInfo.setUid( 0); long timeNow = System.currentTimeMillis(); m_rootInfo.setCreationDateTime( timeNow); m_rootInfo.setAccessDateTime( timeNow); m_rootInfo.setModifyDateTime( timeNow); m_rootInfo.setChangeDateTime( timeNow); // Enable the file state cache enableStateCache(true); // Set the enabled database features if ( hasNTFSStreamsEnabled() == true && m_loader.supportsStreams() == true) dbFeatures |= DBInterface.FeatureNTFS; if ( hasRetentionPeriod() == true) dbFeatures |= DBInterface.FeatureRetention; if ( getDBInterface().supportsFeature( DBInterface.FeatureSymLinks)) dbFeatures |= DBInterface.FeatureSymLinks; try { // Request the required database features to be enabled getDBInterface().requestFeatures(dbFeatures); } catch ( DBException ex) { throw new DeviceContextException("Failed to enable database features, " + ex.getMessage()); } // Initialize the database interface try { // Initialize the database interface getDBInterface().initializeDatabase(this, m_dbifConfig); } catch (InvalidConfigurationException ex) { throw new DeviceContextException("Database interface initialization failure, " + ex.toString()); } // Set the default file state cache timeout getStateCache().setCacheTimer(m_cacheTimer); getStateCache().setCheckInterval(Math.max(5000,m_cacheTimer/4)); // Initialize the file loader, if it is a seperate class from the database interface if ( m_loaderClass != null) { try { // Initialize the file loader m_loader.initializeLoader(m_loaderConfig, this); } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Rethrow the exception throw new DeviceContextException(ex.getMessage()); } } } /** * Return the database interface class name * * @return String */ public final String getDBInterfaceClass() { return m_dbifClass; } /** * Return the database interface class * * @return DBInterface */ public final DBInterface getDBInterface() { return m_dbInterface; } /** * Return the database interface configuration parameters * * @return ConfigElement */ public final ConfigElement getDBInterfaceConfiguration() { return m_dbifConfig; } /** * Return the file loader class name * * @return String */ public final String getFileLoaderClass() { return m_loaderClass; } /** * Return the file data loader class * * @return FileLoader */ public final FileLoader getFileLoader() { return m_loader; } /** * Return the file loader class arguments list * * @return ConfigElement */ public final ConfigElement getLoaderConfiguration() { return m_loaderConfig; } /** * Return the file state cache timeout, in milliseconds * * @return long */ public final long getCacheTimeout() { return m_cacheTimer; } /** * Determine if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Check if files should be marked as offline * * @return boolean */ public final boolean hasOfflineFiles() { return m_offlineFiles; } /** * Check if files are only to be marked offline above a certain size * * @return boolean */ public final boolean hasOfflineFileSize() { return m_offlineFileSize > 0; } /** * Return the file size to mark as offline * * @return long */ public final long getOfflineFileSize() { return m_offlineFileSize; } /** * Determine if the retention period is enabled * * @return boolean */ public final boolean hasRetentionPeriod() { return m_retentionPeriod != -1L ? true : false; } /** * Return the retention period, in milliseconds * * @return long */ public final long getRetentionPeriod() { return m_retentionPeriod; } /** * Return the root directory file information * * @return DBFileInfo */ public final DBFileInfo getRootDirectoryInfo() { return m_rootInfo; } /** * Check if NTFS streams are enabled * * @return boolean */ public final boolean hasNTFSStreamsEnabled() { return m_ntfsStreams; } /** * Determine if the trashcan feature is enabled * * @return boolean */ public final boolean isTrashCanEnabled() { return m_trashCan; } /** * Check if a mamximum file size has been specified * * @return boolean */ public final boolean hasMaximumFileSize() { return m_maxFileSize != 0 ? true : false; } /** * Return the maximum file size allowed on this filesystem * * @return long */ public final long getMaximumFileSize() { return m_maxFileSize; } /** * Check if there are files to be deleted in the offline delete list * * @return boolean */ public final boolean hasOfflineFileDeletes() { return m_offlineDeleteList != null; } /** * Return the offline file delete list * * @param clearList boolean * @return FileRequestQueue */ public synchronized final FileRequestQueue getOfflineFileDeletes(boolean clearList) { FileRequestQueue deleteList = m_offlineDeleteList; if ( clearList == true) m_offlineDeleteList = null; return deleteList; } /** * Add an offline file delete request * * @param deleteReq DeleteFileRequest */ public synchronized final void addOfflineFileDelete(DeleteFileRequest deleteReq) { if ( m_offlineDeleteList == null) m_offlineDeleteList = new FileRequestQueue(); m_offlineDeleteList.addRequest( deleteReq); } /** * Determine if the connection has a file state cache * * @return boolean */ public final boolean hasStateCache() { return m_stateCache != null ? true : false; } /** * Return the file state cache * * @return FileStateCache */ public final FileStateCache getStateCache() { return m_stateCache; } /** * Enable/disable the file state cache * * @param ena boolean */ public final void enableStateCache(boolean ena) { if ( ena == true) { if ( m_stateCache == null) m_stateCache = new FileStateCache(); } else m_stateCache = null; } /** * Remove expired file states from the state cache * * @return int */ public final int removeExpiredFileStates() { // Check if there is a file state cache if ( hasStateCache() == false) return 0; // Remove expired file states from the state cache return m_stateCache.removeExpiredFileStates(); } /** * File state has expired. The listener can control whether the file state is removed * from the cache, or not. * * @param state FileState * @return true to remove the file state from the cache, or false to leave the file state in the cache */ public boolean fileStateExpired(FileState state) { // Check if the file state has an associated file String tempName = (String) state.findAttribute(DBNetworkFile.DBCacheFile); if ( tempName != null) { // Check if the file is open if ( state.getOpenCount() > 0) { // Do not expire the file state yet as the temporary file is in use return false; } else { // Delete the temporary file File tempFile = new File(tempName); tempFile.delete(); // Debug if ( m_debug) Debug.println("$$ Deleted temporary file " + tempName + " (Expired) $$"); } } // File is not open or does not have a temporary file, file state can be expired return true; } /** * File state cache is closing down, any resources attached to the file state must be released. * * @param state FileState */ public void fileStateClosed(FileState state) { // Check if the file state has an associated file String tempName = (String) state.findAttribute(DBNetworkFile.DBCacheFile); if ( tempName != null) { // Delete the temporary file File tempFile = new File(tempName); tempFile.delete(); // Debug if ( m_debug) Debug.println("$$ Deleted temporary file " + tempName + " (Closed) $$"); } } /** * Return the JDBC context as a string * * @return String */ public String toString() { StringBuffer str = new StringBuffer(); str.append("["); str.append(getDBInterface().getDBInterfaceName()); str.append(","); str.append(getFileLoaderClass()); str.append("]"); return str.toString(); } /** * Close the device context */ public void CloseContext() { // Close the file loader if ( getFileLoader() != null) getFileLoader().shutdownLoader(false); // Close the database interface if ( getDBInterface() != null) getDBInterface().shutdownDatabase( this); // Check if the file state cache is enabled, if so then release the file states and associated // resources. if ( hasStateCache()) { m_stateCache.removeAllFileStates(); getStateCache().shutdownRequest(); } // Call the base class super.CloseContext(); } /** * Start the shared filesystem, perform startup processing here. * * @param disk DiskSharedDevice * @throws DeviceContextException */ public void startFilesystem(DiskSharedDevice disk) throws DeviceContextException { // Start the quota manager, if configured if ( hasQuotaManager()) { try { // Start the quota manager getQuotaManager().startManager(disk.getDiskInterface(), this); } catch (QuotaManagerException ex) { throw new DeviceContextException(ex.toString()); } } } }