Author: asmuts Date: Thu Jun 18 17:16:55 2009 New Revision: 786172 URL: http://svn.apache.org/viewvc?rev=786172&view=rev Log: Added a FileDiskCache based on the sketch provided in https://issues.apache.org/jira/browse/JCS-58
Added: jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/ jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCache.java jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheAttributes.java jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactory.java jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheManager.java jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/ jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactoryUnitTest.java jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheUnitTest.java Modified: jakarta/jcs/trunk/src/test-conf/log4j.properties Added: jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCache.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCache.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCache.java (added) +++ jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCache.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,546 @@ +package org.apache.jcs.auxiliary.disk.file; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes; +import org.apache.jcs.auxiliary.disk.AbstractDiskCache; +import org.apache.jcs.engine.behavior.ICacheElement; +import org.apache.jcs.engine.behavior.IElementSerializer; +import org.apache.jcs.engine.logging.behavior.ICacheEvent; +import org.apache.jcs.engine.logging.behavior.ICacheEventLogger; +import org.apache.jcs.utils.timing.SleepUtil; + +/** + * This disk cache writes each item to a separate file. This is for regions with very few items, + * perhaps big ones. + * <p> + * This is a fairly simple implementation. All the disk writing is handled right here. It's not + * clear that anything more complicated is needed. + */ +public class FileDiskCache + extends AbstractDiskCache +{ + /** Don't change */ + private static final long serialVersionUID = 1L; + + /** The logger. */ + private static final Log log = LogFactory.getLog( FileDiskCache.class ); + + /** The name to prefix all log messages with. */ + private final String logCacheName; + + /** The config values. */ + private FileDiskCacheAttributes diskFileCacheAttributes; + + /** The directory where the files are stored */ + private File directory; + + /** + * Constructor for the DiskCache object. + * <p> + * @param cacheAttributes + */ + public FileDiskCache( FileDiskCacheAttributes cacheAttributes ) + { + this( cacheAttributes, null ); + } + + /** + * Constructor for the DiskCache object. Will be marked alive if the directory cannot be + * created. + * <p> + * @param cattr + * @param elementSerializer used if supplied, the super's super will not set a null + */ + public FileDiskCache( FileDiskCacheAttributes cattr, IElementSerializer elementSerializer ) + { + super( cattr ); + setElementSerializer( elementSerializer ); + this.diskFileCacheAttributes = cattr; + this.logCacheName = "Region [" + getCacheName() + "] "; + alive = initializeFileSystem( cattr );; + } + + /** + * Tries to create the root directory if it does not already exist. + * <p> + * @param cattr + * @return does the directory exist. + */ + private boolean initializeFileSystem( FileDiskCacheAttributes cattr ) + { + // TODO, we might need to make this configurable + String rootDirName = cattr.getDiskPath() + "/" + cattr.getCacheName(); + this.setDirectory( new File( rootDirName ) ); + boolean createdDirectories = getDirectory().mkdirs(); + if ( log.isInfoEnabled() ) + { + log.info( logCacheName + "Cache file root directory: " + rootDirName ); + log.info( logCacheName + "Created root directory: " + createdDirectories ); + } + + // TODO consider throwing. + boolean exists = getDirectory().exists(); + if ( !exists ) + { + log.error( "Could not initialize File Disk Cache. The root directory does not exist." ); + } + return exists; + } + + /** + * Creates the file for a key. Filenames and keys can be passed into this method. It must be + * idempotent. + * <p> + * Protected for testing. + * <p> + * @param key + * @return the file for the key + */ + protected File file( Serializable key ) + { + StringBuffer fileNameBuffer = new StringBuffer(); + + // add key as filename in a file system safe way + String keys = key.toString(); + int l = keys.length(); + for ( int i = 0; i < l; i++ ) + { + char c = keys.charAt( i ); + if ( !Character.isLetterOrDigit( c ) ) + { + c = '_'; + } + fileNameBuffer.append( c ); + } + String fileName = fileNameBuffer.toString(); + + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Creating file for name: [" + fileName + "] based on key: [" + key + "]" ); + } + + return new File( getDirectory().getAbsolutePath(), fileName ); + } + + /** + * @param groupName + * @return Set + */ + public Set getGroupKeys( String groupName ) + { + throw new UnsupportedOperationException(); + } + + /** + * @return dir.list().length + */ + public int getSize() + { + if ( getDirectory().exists() ) + { + return getDirectory().list().length; + } + return 0; + } + + /** + * @return AuxiliaryCacheAttributes + */ + public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes() + { + return diskFileCacheAttributes; + } + + /** + * @return String the path to the directory + */ + protected String getDiskLocation() + { + return getDirectory().getAbsolutePath(); + } + + /** + * Sets alive to false. + * <p> + * @throws IOException + */ + protected synchronized void processDispose() + throws IOException + { + ICacheEvent cacheEvent = createICacheEvent( cacheName, "none", ICacheEventLogger.DISPOSE_EVENT ); + try + { + if ( !alive ) + { + log.error( logCacheName + "Not alive and dispose was called, directgory: " + getDirectory() ); + return; + } + + // Prevents any interaction with the cache while we're shutting down. + alive = false; + + // TODO consider giving up the handle on the directory. + if ( log.isInfoEnabled() ) + { + log.info( logCacheName + "Shutdown complete." ); + } + } + finally + { + logICacheEvent( cacheEvent ); + } + } + + /** + * Looks for a file matching the key. If it exists, reads the file. + * <p> + * @param key + * @return ICacheElement + * @throws IOException + */ + protected ICacheElement processGet( Serializable key ) + throws IOException + { + File file = file( key ); + + if ( !file.exists() ) + { + if ( log.isDebugEnabled() ) + { + log.debug( "File does not exist. Returning null from Get." + file ); + } + return null; + } + + ICacheElement element = null; + + FileInputStream fis = null; + try + { + fis = new FileInputStream( file ); + + long length = file.length(); + // Create the byte array to hold the data + byte[] bytes = new byte[(int) length]; + + int offset = 0; + int numRead = 0; + while ( offset < bytes.length && ( numRead = fis.read( bytes, offset, bytes.length - offset ) ) >= 0 ) + { + offset += numRead; + } + + // Ensure all the bytes have been read in + if ( offset < bytes.length ) + { + throw new IOException( "Could not completely read file " + file.getName() ); + } + + element = (ICacheElement) getElementSerializer().deSerialize( bytes ); + + // test that the retrieved object has equal key + if ( element != null && !key.equals( element.getKey() ) ) + { + if ( log.isInfoEnabled() ) + { + log.info( logCacheName + "key: [" + key + "] point to cached object with key: [" + element.getKey() + + "]" ); + } + element = null; + } + } + catch ( IOException e ) + { + log.error( logCacheName + "Failure getting element, key: [" + key + "]", e ); + } + catch ( ClassNotFoundException e ) + { + log.error( logCacheName + "Failure getting element, key: [" + key + "]", e ); + } + finally + { + silentClose( fis ); + } + + // If this is true and we have a max file size, the Least Recently Used file will be removed. + if ( element != null && diskFileCacheAttributes.isTouchOnGet() ) + { + touchWithRetry( file ); + } + return element; + } + + /** + * @param pattern + * @return Map + * @throws IOException + */ + protected Map processGetMatching( String pattern ) + throws IOException + { + // TODO get a list of file and return those with matching keys. + // the problem will be to handle the underscores. + return null; + } + + /** + * Removes the file. + * <p> + * @param key + * @return true if the item was removed + * @throws IOException + */ + protected boolean processRemove( Serializable key ) + throws IOException + { + File file = file( key ); + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Removing file " + file ); + } + return deleteWithRetry( file ); + } + + /** + * Remove all the files in the directory. + * <p> + * Assumes that this is the only region in the directory. We could add a region prefix to the + * files and only delete those, but the region should create a directory. + * <p> + * @throws IOException + */ + protected void processRemoveAll() + throws IOException + { + String[] fileNames = getDirectory().list(); + for ( int i = 0; i < fileNames.length; i++ ) + { + processRemove( fileNames[i] ); + } + } + + /** + * We create a temp file with the new contents, remove the old if it exists, and then rename the + * temp. + * <p> + * @param element + * @throws IOException + */ + protected void processUpdate( ICacheElement element ) + throws IOException + { + removeIfLimitIsSetAndReached(); + + File file = file( element.getKey() ); + + File tmp = null; + OutputStream os = null; + try + { + byte[] bytes = getElementSerializer().serialize( element ); + + tmp = File.createTempFile( "JCS_DiskFileCache", null, getDirectory() ); + + FileOutputStream fos = new FileOutputStream( tmp ); + os = new BufferedOutputStream( fos ); + + if ( bytes != null ) + { + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Wrote " + bytes.length + " bytes to file " + tmp ); + } + os.write( bytes ); + os.close(); + } + deleteWithRetry( file ); + tmp.renameTo( file ); + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Renamed to: " + file ); + } + } + catch ( IOException e ) + { + log.error( logCacheName + "Failure updating element, key: [" + element.getKey() + "]", e ); + } + finally + { + silentClose( os ); + if ( ( tmp != null ) && tmp.exists() ) + { + deleteWithRetry( tmp ); + } + } + } + + /** + * If a limit has been set and we have reached it, remove the least recently modified file. + * <p> + * We will probably need to touch the files. If we touch, the LRM file will be based on age + * (i.e. FIFO). If we touch, it will be based on access time (i.e. LRU). + */ + private void removeIfLimitIsSetAndReached() + { + if ( diskFileCacheAttributes.getMaxNumberOfFiles() > 0 ) + { + // TODO we might want to synchronize this block. + if ( getSize() >= diskFileCacheAttributes.getMaxNumberOfFiles() ) + { + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Max reached, removing least recently modifed" ); + } + + long oldestLastModified = System.currentTimeMillis(); + File theLeastRecentlyModified = null; + String[] fileNames = getDirectory().list(); + for ( int i = 0; i < fileNames.length; i++ ) + { + File file = file( fileNames[i] ); + long lastModified = file.lastModified(); + if ( lastModified < oldestLastModified ) + { + oldestLastModified = lastModified; + theLeastRecentlyModified = file; + } + } + if ( theLeastRecentlyModified != null ) + { + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Least recently modifed: " + theLeastRecentlyModified ); + } + deleteWithRetry( theLeastRecentlyModified ); + } + } + } + } + + /** + * Tries to delete a file. If it fails, it tries several more times, pausing a few ms. each + * time. + * <p> + * @param file + * @return true if the file does not exist or if it was removed + */ + private boolean deleteWithRetry( File file ) + { + boolean success = file.delete(); + + if ( file.exists() ) + { + int maxRetries = diskFileCacheAttributes.getMaxRetriesOnDelete(); + for ( int i = 0; i < maxRetries && !success; i++ ) + { + SleepUtil.sleepAtLeast( 5 ); + success = file.delete(); + } + } + else + { + success = true; + } + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "deleteWithRetry. success= " + success + " file: " + file ); + } + return success; + } + + /** + * Tries to set the last access time to now. + * <p> + * @param file to touch + * @return was it successful + */ + private boolean touchWithRetry( File file ) + { + boolean success = file.setLastModified( System.currentTimeMillis() ); + if ( !success ) + { + int maxRetries = diskFileCacheAttributes.getMaxRetriesOnTouch(); + if ( file.exists() ) + { + for ( int i = 0; i < maxRetries && !success; i++ ) + { + SleepUtil.sleepAtLeast( 5 ); + success = file.delete(); + } + } + } + if ( log.isDebugEnabled() ) + { + log.debug( logCacheName + "Last modified, success: " + success ); + } + return success; + } + + /** + * Closes a stream and swallows errors. + * <p> + * @param s the stream + */ + private void silentClose( InputStream s ) + { + if ( s != null ) + { + try + { + s.close(); + } + catch ( IOException e ) + { + log.error( logCacheName + "Failure closing stream", e ); + } + } + } + + /** + * Closes a stream and swallows errors. + * <p> + * @param s the stream + */ + private void silentClose( OutputStream s ) + { + if ( s != null ) + { + try + { + s.close(); + } + catch ( IOException e ) + { + log.error( logCacheName + "Failure closing stream", e ); + } + } + } + + /** + * @param directory the directory to set + */ + protected void setDirectory( File directory ) + { + this.directory = directory; + } + + /** + * @return the directory + */ + protected File getDirectory() + { + return directory; + } +} Added: jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheAttributes.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheAttributes.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheAttributes.java (added) +++ jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheAttributes.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,139 @@ +package org.apache.jcs.auxiliary.disk.file; + +import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes; +import org.apache.jcs.auxiliary.disk.AbstractDiskCacheAttributes; + +/** + * Configuration values for the file disk cache. + */ +public class FileDiskCacheAttributes + extends AbstractDiskCacheAttributes +{ + /** Don't change. */ + private static final long serialVersionUID = -7371586172678836062L; + + /** Default file count limit: -1 means no limit */ + public static final int DEFAULT_MAX_NUMBER_OF_FILES = -1; + + /** Max number of files */ + private int maxNumberOfFiles = DEFAULT_MAX_NUMBER_OF_FILES; + + /** Default limit on the number of times we will retry a delete. */ + public static final int DEFAULT_MAX_RETRIES_ON_DELETE = 10; + + /** Max number of retries on delete */ + private int maxRetriesOnDelete = DEFAULT_MAX_RETRIES_ON_DELETE; + + /** Default touch rule. */ + public static final boolean DEFAULT_TOUCH_ON_GET = false; + + /** Default limit on the number of times we will retry a delete. */ + public static final int DEFAULT_MAX_RETRIES_ON_TOUCH = 10; + + /** Max number of retries on touch */ + private int maxRetriesOnTouch = DEFAULT_MAX_RETRIES_ON_TOUCH; + + /** + * Should we touch on get. If so, we will reset the last modified time. If you have a max file + * size set, this will make the removal strategy LRU. If this is false, then the oldest will be + * removed. + */ + private boolean touchOnGet = DEFAULT_TOUCH_ON_GET; + + /** + * Returns a copy of the attributes. + * <p> + * @return AuxiliaryCacheAttributes + */ + public AuxiliaryCacheAttributes copy() + { + try + { + return (AuxiliaryCacheAttributes) this.clone(); + } + catch ( Exception e ) + { + // swallow + } + return this; + } + + /** + * @param maxNumberOfFiles the maxNumberOfFiles to set + */ + public void setMaxNumberOfFiles( int maxNumberOfFiles ) + { + this.maxNumberOfFiles = maxNumberOfFiles; + } + + /** + * @return the maxNumberOfFiles + */ + public int getMaxNumberOfFiles() + { + return maxNumberOfFiles; + } + + /** + * @param maxRetriesOnDelete the maxRetriesOnDelete to set + */ + public void setMaxRetriesOnDelete( int maxRetriesOnDelete ) + { + this.maxRetriesOnDelete = maxRetriesOnDelete; + } + + /** + * @return the maxRetriesOnDelete + */ + public int getMaxRetriesOnDelete() + { + return maxRetriesOnDelete; + } + + /** + * @param touchOnGet the touchOnGet to set + */ + public void setTouchOnGet( boolean touchOnGet ) + { + this.touchOnGet = touchOnGet; + } + + /** + * @return the touchOnGet + */ + public boolean isTouchOnGet() + { + return touchOnGet; + } + + /** + * @param maxRetriesOnTouch the maxRetriesOnTouch to set + */ + public void setMaxRetriesOnTouch( int maxRetriesOnTouch ) + { + this.maxRetriesOnTouch = maxRetriesOnTouch; + } + + /** + * @return the maxRetriesOnTouch + */ + public int getMaxRetriesOnTouch() + { + return maxRetriesOnTouch; + } + + /** + * Write out the values for debugging purposes. + * <p> + * @return String + */ + public String toString() + { + StringBuffer str = new StringBuffer(); + str.append( "DiskFileCacheAttributes " ); + str.append( "\n diskPath = " + diskPath ); + str.append( "\n maxNumberOfFiles = " + getMaxNumberOfFiles() ); + str.append( "\n maxRetriesOnDelete = " + getMaxRetriesOnDelete() ); + return str.toString(); + } +} Added: jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactory.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactory.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactory.java (added) +++ jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactory.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,77 @@ +package org.apache.jcs.auxiliary.disk.file; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jcs.auxiliary.AuxiliaryCache; +import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes; +import org.apache.jcs.auxiliary.AuxiliaryCacheFactory; +import org.apache.jcs.engine.behavior.ICompositeCacheManager; +import org.apache.jcs.engine.behavior.IElementSerializer; +import org.apache.jcs.engine.logging.behavior.ICacheEventLogger; + +/** Create Disk File Caches */ +public class FileDiskCacheFactory + implements AuxiliaryCacheFactory +{ + /** The logger. */ + private final static Log log = LogFactory.getLog( FileDiskCacheFactory.class ); + + /** The auxiliary name. */ + private String name; + + /** The manager used by this factory instance */ + private FileDiskCacheManager diskFileCacheManager; + + /** + * Creates a manager if we don't have one, and then uses the manager to create the cache. The + * same factory will be called multiple times by the composite cache to create a cache for each + * region. + * <p> + * @param attr config + * @param cacheMgr the manager to use if needed + * @param cacheEventLogger the event logger + * @param elementSerializer the serializer + * @return AuxiliaryCache + */ + public AuxiliaryCache createCache( AuxiliaryCacheAttributes attr, ICompositeCacheManager cacheMgr, + ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer ) + { + FileDiskCacheAttributes idfca = (FileDiskCacheAttributes) attr; + synchronized( this ) + { + if ( diskFileCacheManager == null ) + { + if ( log.isDebugEnabled() ) + { + log.debug( "Creating DiskFileCacheManager" ); + } + diskFileCacheManager = new FileDiskCacheManager( idfca, cacheEventLogger, elementSerializer ); + } + } + if ( log.isDebugEnabled() ) + { + log.debug( "Creating DiskFileCache for attributes = " + idfca ); + } + return diskFileCacheManager.getCache( idfca ); + } + + /** + * Gets the name attribute of the DiskCacheFactory object + * <p> + * @return The name value + */ + public String getName() + { + return this.name; + } + + /** + * Sets the name attribute of the DiskCacheFactory object + * <p> + * @param name The new name value + */ + public void setName( String name ) + { + this.name = name; + } +} Added: jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheManager.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheManager.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheManager.java (added) +++ jakarta/jcs/trunk/src/java/org/apache/jcs/auxiliary/disk/file/FileDiskCacheManager.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,105 @@ +package org.apache.jcs.auxiliary.disk.file; + +import java.util.Hashtable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jcs.auxiliary.AuxiliaryCache; +import org.apache.jcs.auxiliary.disk.AbstractDiskCacheManager; +import org.apache.jcs.engine.behavior.IElementSerializer; +import org.apache.jcs.engine.logging.behavior.ICacheEventLogger; + +/** + * This is a non singleton. It creates caches on a per region basis. + */ +public class FileDiskCacheManager + extends AbstractDiskCacheManager +{ + /** Don't change */ + private static final long serialVersionUID = -4153287154512264626L; + + /** The logger */ + private final static Log log = LogFactory.getLog( FileDiskCacheManager.class ); + + /** Each region has an entry here. */ + private Hashtable caches = new Hashtable(); + + /** User configurable attributes */ + private FileDiskCacheAttributes defaultCacheAttributes; + + /** + * Constructor for the DiskFileCacheManager object + * <p> + * @param defaultCacheAttributes Default attributes for caches managed by the instance. + * @param cacheEventLogger + * @param elementSerializer + */ + protected FileDiskCacheManager( FileDiskCacheAttributes defaultCacheAttributes, ICacheEventLogger cacheEventLogger, + IElementSerializer elementSerializer ) + { + this.defaultCacheAttributes = defaultCacheAttributes; + setElementSerializer( elementSerializer ); + setCacheEventLogger( cacheEventLogger ); + } + + /** + * Gets an DiskFileCache for the supplied name using the default attributes. + * <p> + * @param cacheName Name that will be used when creating attributes. + * @return A cache. + */ + public AuxiliaryCache getCache( String cacheName ) + { + FileDiskCacheAttributes cacheAttributes = (FileDiskCacheAttributes) defaultCacheAttributes.copy(); + + cacheAttributes.setCacheName( cacheName ); + + return getCache( cacheAttributes ); + } + + /** + * Get an DiskFileCache for the supplied attributes. Will provide an existing cache for the name + * attribute if one has been created, or will create a new cache. + * <p> + * @param cacheAttributes Attributes the cache should have. + * @return A cache, either from the existing set or newly created. + */ + public AuxiliaryCache getCache( FileDiskCacheAttributes cacheAttributes ) + { + AuxiliaryCache cache = null; + + String cacheName = cacheAttributes.getCacheName(); + + log.debug( "Getting cache named: " + cacheName ); + + synchronized ( caches ) + { + // Try to load the cache from the set that have already been + // created. This only looks at the name attribute. + + cache = (AuxiliaryCache) caches.get( cacheName ); + + // If it was not found, create a new one using the supplied + // attributes + + if ( cache == null ) + { + cache = new FileDiskCache( cacheAttributes, getElementSerializer() ); + cache.setCacheEventLogger( getCacheEventLogger() ); + caches.put( cacheName, cache ); + } + } + + return cache; + } + + /** + * Gets the cacheType attribute of the DiskCacheManager object + * <p> + * @return The cacheType value + */ + public int getCacheType() + { + return DISK_CACHE; + } +} Modified: jakarta/jcs/trunk/src/test-conf/log4j.properties URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/test-conf/log4j.properties?rev=786172&r1=786171&r2=786172&view=diff ============================================================================== --- jakarta/jcs/trunk/src/test-conf/log4j.properties (original) +++ jakarta/jcs/trunk/src/test-conf/log4j.properties Thu Jun 18 17:16:55 2009 @@ -22,7 +22,8 @@ log4j.category.org.apache.jcs.engine.CacheEventQueueFactory=INFO log4j.category.org.apache.jcs.auxiliary.disk.jdbc=INFO log4j.category.org.apache.jcs.auxiliary.disk=INFO -log4j.category.org.apache.jcs.auxiliary.disk.block=DEBUG +log4j.category.org.apache.jcs.auxiliary.disk.block=INFO +log4j.category.org.apache.jcs.auxiliary.disk.file=INFO log4j.category.org.apache.jcs.auxiliary.remote=INFO log4j.category.org.apache.jcs.auxiliary.lateral=INFO log4j.category.org.apache.jcs.utils.struct=INFO Added: jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactoryUnitTest.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactoryUnitTest.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactoryUnitTest.java (added) +++ jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheFactoryUnitTest.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,39 @@ +package org.apache.jcs.auxiliary.disk.file; + +import junit.framework.TestCase; + +import org.apache.jcs.auxiliary.MockCacheEventLogger; +import org.apache.jcs.engine.behavior.ICompositeCacheManager; +import org.apache.jcs.engine.behavior.IElementSerializer; +import org.apache.jcs.engine.control.MockCompositeCacheManager; +import org.apache.jcs.engine.control.MockElementSerializer; +import org.apache.jcs.engine.logging.behavior.ICacheEventLogger; + +/** Verify that the factory works */ +public class FileDiskCacheFactoryUnitTest + extends TestCase +{ + /** Verify that we can get a cache from the manager via the factory */ + public void testCreateCache_Normal() + { + // SETUP + String cacheName = "testCreateCache_Normal"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/FileDiskCacheFactoryUnitTest" ); + + ICompositeCacheManager cacheMgr = new MockCompositeCacheManager(); + ICacheEventLogger cacheEventLogger = new MockCacheEventLogger(); + IElementSerializer elementSerializer = new MockElementSerializer(); + + FileDiskCacheFactory factory = new FileDiskCacheFactory(); + + // DO WORK + FileDiskCache result = (FileDiskCache) factory.createCache( cattr, cacheMgr, cacheEventLogger, + elementSerializer ); + + // VERIFY + assertNotNull( "Should have a disk cache", result ); + assertEquals( "Should have a disk cache with a serializer", elementSerializer, result.getElementSerializer() ); + } +} Added: jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheUnitTest.java URL: http://svn.apache.org/viewvc/jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheUnitTest.java?rev=786172&view=auto ============================================================================== --- jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheUnitTest.java (added) +++ jakarta/jcs/trunk/src/test/org/apache/jcs/auxiliary/disk/file/FileDiskCacheUnitTest.java Thu Jun 18 17:16:55 2009 @@ -0,0 +1,522 @@ +package org.apache.jcs.auxiliary.disk.file; + +import java.io.File; + +import junit.framework.TestCase; + +import org.apache.jcs.engine.CacheConstants; +import org.apache.jcs.engine.CacheElement; +import org.apache.jcs.engine.behavior.ICacheElement; +import org.apache.jcs.utils.timing.SleepUtil; + +/** Unit tests for the disk file cache. */ +public class FileDiskCacheUnitTest + extends TestCase +{ + /** + * Verify initialization. + * <p> + * @throws Exception + */ + public void testInitialization_Normal() + throws Exception + { + // SETUP + String cacheName = "testInitialization_Normal"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + + // DO WORK + FileDiskCache diskCache = new FileDiskCache( cattr ); + File directory = diskCache.getDirectory(); + + // VERIFY + assertNotNull( "Should have a directory", directory ); + assertTrue( "Should have an existing directory", directory.exists() ); + assertTrue( "Directory should include the cache name. " + directory.getAbsolutePath(), directory + .getAbsolutePath().indexOf( cacheName ) != -1 ); + assertTrue( "Directory should include the disk path. " + directory.getAbsolutePath(), directory + .getAbsolutePath().indexOf( "DiskFileCacheUnitTest" ) != -1 ); + assertTrue( "Should be alive", diskCache.getStatus() == CacheConstants.STATUS_ALIVE ); + } + + /** + * Verify dispose. + * <p> + * @throws Exception + */ + public void testDispose_Normal() + throws Exception + { + // SETUP + String cacheName = "testDispose_Normal"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + // DO WORK + diskCache.dispose(); + + // VERIFY + assertTrue( "Should not be alive", diskCache.getStatus() == CacheConstants.STATUS_DISPOSED ); + } + + /** + * Verify initialization. + * <p> + * @throws Exception + */ + public void testInitialization_JunkFileName() + throws Exception + { + // SETUP + String cacheName = "testInitialization_JunkFileName"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest%$&*#@" ); + + // DO WORK + FileDiskCache diskCache = new FileDiskCache( cattr ); + File directory = diskCache.getDirectory(); + + // VERIFY + assertNotNull( "Should have a directory", directory ); + assertFalse( "Should have an existing directory", directory.exists() ); + assertTrue( "Should not be alive", diskCache.getStatus() == CacheConstants.STATUS_DISPOSED ); + } + + /** + * Verify getSize. + * <p> + * @throws Exception + */ + public void testGetSize_Empty() + throws Exception + { + // SETUP + String cacheName = "testGetSize_Empty"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + diskCache.removeAll(); + + // DO WORK + int result = diskCache.getSize(); + + // VERIFY + assertEquals( "Should be empty.", 0, result ); + } + + /** + * Verify getSize. + * <p> + * @throws Exception + */ + public void testGetSize_OneItem() + throws Exception + { + // SETUP + String cacheName = "testGetSize_OneItem"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + diskCache.removeAll(); + diskCache.update( new CacheElement( cacheName, "key1", "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + int result = diskCache.getSize(); + + // VERIFY + assertEquals( "Should not be empty.", 1, result ); + } + + /** + * Verify remove all. + * <p> + * @throws Exception + */ + public void testRemoveAll_OneItem() + throws Exception + { + // SETUP + String cacheName = "testRemoveAll_OneItem"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + diskCache.update( new CacheElement( cacheName, "key1", "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + diskCache.removeAll(); + SleepUtil.sleepAtLeast( 100 ); + int result = diskCache.getSize(); + + // VERIFY + assertEquals( "Should be empty.", 0, result ); + } + + /** + * Verify get. + * <p> + * @throws Exception + */ + public void testGet_Empty() + throws Exception + { + // SETUP + String cacheName = "testGet_Empty"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + // DO WORK + ICacheElement result = diskCache.get( "key" ); + + // VERIFY + assertNull( "Should be null.", result ); + } + + /** + * Verify get. + * <p> + * @throws Exception + */ + public void testGet_Exists() + throws Exception + { + // SETUP + String cacheName = "testGet_Empty"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + diskCache.update( new CacheElement( cacheName, "key1", "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + ICacheElement result = diskCache.get( "key1" ); + + // VERIFY + assertNotNull( "Should NOT be null.", result ); + } + + /** + * Verify RemoveIfLimitIsSetAndReached. + * <p> + * @throws Exception + */ + public void testRemoveIfLimitIsSetAndReached_NotReached() + throws Exception + { + // SETUP + int maxNumberOfFiles = 10; + String cacheName = "testRemoveIfLimitIsSetAndReached_NotReached"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + cattr.setMaxNumberOfFiles( maxNumberOfFiles ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + for ( int i = 0; i < maxNumberOfFiles; i++ ) + { + diskCache.update( new CacheElement( cacheName, "key" + i, "Data" ) ); + } + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + ICacheElement result = diskCache.get( "key0" ); + + // VERIFY + assertNotNull( "Should NOT be null.", result ); + } + + /** + * Verify RemoveIfLimitIsSetAndReached. + * <p> + * @throws Exception + */ + public void testRemoveIfLimitIsSetAndReached_Reached() + throws Exception + { + // SETUP + int maxNumberOfFiles = 10; + String cacheName = "testRemoveIfLimitIsSetAndReached_Reached"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + cattr.setMaxNumberOfFiles( maxNumberOfFiles ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + for ( int i = 0; i <= maxNumberOfFiles; i++ ) + { + diskCache.update( new CacheElement( cacheName, "key" + i, "Data" ) ); + } + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + ICacheElement result = diskCache.get( "key0" ); + + // VERIFY + assertNull( "Should be null.", result ); + } + + /** + * Verify RemoveIfLimitIsSetAndReached. Since touch on get is true, the LRU and not the oldest + * shoudl be removed. + * <p> + * @throws Exception + */ + public void testRemoveIfLimitIsSetAndReached_Reached_TouchTrue() + throws Exception + { + // SETUP + int maxNumberOfFiles = 10; + String cacheName = "testRemoveIfLimitIsSetAndReached_Reached_TouchTrue"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + cattr.setMaxNumberOfFiles( maxNumberOfFiles ); + cattr.setTouchOnGet( true ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + diskCache.removeAll(); + + for ( int i = 0; i < maxNumberOfFiles; i++ ) + { + diskCache.update( new CacheElement( cacheName, "key" + i, "Data" ) ); + } + SleepUtil.sleepAtLeast( 100 ); + + for ( int i = maxNumberOfFiles - 1; i >= 0; i-- ) + { + SleepUtil.sleepAtLeast( 5 ); + diskCache.get( "key" + i ); + } + SleepUtil.sleepAtLeast( 100 ); + + // This will push it over. number 9, the youngest, but LRU item should be removed + diskCache.update( new CacheElement( cacheName, "key" + maxNumberOfFiles, "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + ICacheElement result = diskCache.get( "key9" ); + + // VERIFY + assertNull( "Should be null.", result ); + } + + /** + * Verify RemoveIfLimitIsSetAndReached. Since touch on get is false, the the oldest + * should be removed. + * <p> + * @throws Exception + */ + public void testRemoveIfLimitIsSetAndReached_Reached_TouchFalse() + throws Exception + { + // SETUP + int maxNumberOfFiles = 10; + String cacheName = "testRemoveIfLimitIsSetAndReached_Reached_TouchTrue"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + cattr.setMaxNumberOfFiles( maxNumberOfFiles ); + cattr.setTouchOnGet( false ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + diskCache.removeAll(); + + for ( int i = 0; i < maxNumberOfFiles; i++ ) + { + diskCache.update( new CacheElement( cacheName, "key" + i, "Data" ) ); + } + SleepUtil.sleepAtLeast( 100 ); + + for ( int i = maxNumberOfFiles - 1; i >= 0; i-- ) + { + SleepUtil.sleepAtLeast( 5 ); + diskCache.get( "key" + i ); + } + SleepUtil.sleepAtLeast( 100 ); + + // This will push it over. number 0, the oldest should be removed + diskCache.update( new CacheElement( cacheName, "key" + maxNumberOfFiles, "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + ICacheElement result = diskCache.get( "key0" ); + + // VERIFY + assertNull( "Should be null.", result ); + } + /** + * Verify file. + * <p> + * @throws Exception + */ + public void testFile_NoSPecialCharacters() + throws Exception + { + // SETUP + String cacheName = "testFile"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + String key = "simplestring"; + + // DO WORK + File result = diskCache.file( key ); + + // VERIFY + assertEquals( "Wrong string.", key, result.getName() ); + } + + /** + * Verify file. + * <p> + * @throws Exception + */ + public void testFile_Space() + throws Exception + { + // SETUP + String cacheName = "testFile"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + String key = "simple string"; + + // DO WORK + File result = diskCache.file( key ); + + // VERIFY + assertEquals( "Wrong string.", "simple_string", result.getName() ); + } + + /** + * Verify file. + * <p> + * @throws Exception + */ + public void testFile_SpecialCharacter() + throws Exception + { + // SETUP + String cacheName = "testFile"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + String key = "simple%string"; + + // DO WORK + File result = diskCache.file( key ); + + // VERIFY + assertEquals( "Wrong string.", "simple_string", result.getName() ); + } + + /** + * Verify idempotence. + * <p> + * @throws Exception + */ + public void testFile_WithFile() + throws Exception + { + // SETUP + String cacheName = "testFile"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + String key = "simple%string"; + File firstResult = diskCache.file( key ); + + // DO WORK + File result = diskCache.file( firstResult.getName() ); + + // VERIFY + assertEquals( "Wrong string.", "simple_string", result.getName() ); + } + + /** + * Verify remove. + * <p> + * @throws Exception + */ + public void testRemove_OneItem() + throws Exception + { + // SETUP + String cacheName = "testRemove_OneItem"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + diskCache.update( new CacheElement( cacheName, "key1", "Data" ) ); + SleepUtil.sleepAtLeast( 100 ); + + // DO WORK + diskCache.remove( "key1" ); + SleepUtil.sleepAtLeast( 100 ); + int result = diskCache.getSize(); + + // VERIFY + assertEquals( "Should be empty.", 0, result ); + } + + /** + * Verify that the disk file cache can handle a big string. + * <p> + * @throws Exception + */ + public void testPutGet_BigString() + throws Exception + { + // SETUP + String cacheName = "testPutGet_BigString"; + FileDiskCacheAttributes cattr = new FileDiskCacheAttributes(); + cattr.setCacheName( cacheName ); + cattr.setDiskPath( "target/test-sandbox/DiskFileCacheUnitTest" ); + FileDiskCache diskCache = new FileDiskCache( cattr ); + + String string = "This is my big string ABCDEFGH"; + StringBuffer sb = new StringBuffer(); + sb.append( string ); + for ( int i = 0; i < 4; i++ ) + { + sb.append( " " + i + sb.toString() ); // big string + } + string = sb.toString(); + + // DO WORK + diskCache.update( new CacheElement( cacheName, "x", string ) ); + SleepUtil.sleepAtLeast( 300 ); + + // VERIFY + ICacheElement afterElement = diskCache.get( "x" ); + assertNotNull( afterElement ); + System.out.println( "afterElement = " + afterElement ); + String after = (String) afterElement.getVal(); + + assertNotNull( after ); + assertEquals( "wrong string after retrieval", string, after ); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: jcs-dev-unsubscr...@jakarta.apache.org For additional commands, e-mail: jcs-dev-h...@jakarta.apache.org