Package KFM.Cache

Source Code of KFM.Cache.FileCache$FileComparator

/*
*  This software and supporting documentation were developed by
*
*    Siemens Corporate Technology
*    Competence Center Knowledge Management and Business Transformation
*    D-81730 Munich, Germany
*
*    Authors (representing a really great team ;-) )
*            Stefan B. Augustin, Thorbj�rn Hansen, Manfred Langen
*
*  This software is Open Source under GNU General Public License (GPL).
*  Read the text of this license in LICENSE.TXT
*  or look at www.opensource.org/licenses/
*
*  Once more we emphasize, that:
*  THIS SOFTWARE IS MADE AVAILABLE,  AS IS,  WITHOUT ANY WARRANTY
*  REGARDING  THE  SOFTWARE,  ITS  PERFORMANCE OR
*  FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER DISEASES OR
*  ITS CONFORMITY TO ANY SPECIFICATION. THE ENTIRE RISK AS TO QUALITY AND
*  PERFORMANCE OF THE SOFTWARE IS WITH THE USER.
*
*/


package KFM.Cache;

import java.io.*;
import java.util.*;
import com.coolservlets.util.*;
import com.coolservlets.util.LinkedList;
import KFM.Converter; // java.net.URLDecoder from JDK 1.2
import KFM.File.FileUtils;
import KFM.Exceptions.ProgrammerException;
import KFM.log.*;
import KFM.Cache.CachedHTMLItem;

/**
* NOTE: Do not use this class directly, use FileCache2 instead.
*
* File cache implementation. See base class for detailed
* documentation.<br>
* Instead of storing data in memory (as the base class does), it stores
* data on the file system.
*
* <p><b><i>Methods assume that the {@link appl.Portal.GUI.CachedHTMLItem
* CachedHTMLItem} class is used.</i></b>
*
* @see Cache
*
*/
public class FileCache extends com.coolservlets.util.Cache {

    /**
     * Cache directory.
     */
    protected String mCacheDir;

    /**
     * Create a new cache with default values. Default cache size is 128K with
     * no maximum lifetime.
     *
     * @param aCacheDir absolute path name of cache directory
     */
    public FileCache(String aCacheDir) {

        //Our primary data structure is a hash map. The default capacity of 11
        //is too small in almost all cases, so we set it bigger.
        cachedObjectsHash = new HashMap(103);

        lastAccessedList = new LinkedList();
        ageList = new LinkedList();

        createOrReuseCacheDirectory(aCacheDir);
    }

    /**
     * Create a new cache and specify the maximum size for the cache in bytes.
     * Items added to the cache will have no maximum lifetime.
     *
     * @param maxSize the maximum size of the cache in bytes.
     */
    public FileCache(int maxSize, String aCacheDir) {
        this(aCacheDir);
        this.maxSize = maxSize;
    }

    /**
     * Create a new cache and specify the maximum lifetime of objects. The
     * time should be specified in milleseconds. The minimum lifetime of any
     * cache object is 1000 milleseconds (1 second). Additionally, cache
     * expirations have a 1000 millesecond resolution, which means that all
     * objects are guaranteed to be expired within 1000 milliseconds of their
     * maximum lifetime.
     *
     * @param maxLifetime the maximum amount of time objects can exist in
     *    cache before being deleted.
     * @param aCacheDir absolute path name of cache directory
     */
    public FileCache(long maxLifetime, String aCacheDir) {
        this(aCacheDir);
        this.maxLifetime = maxLifetime;
    }

    /**
     * Create a new cache and specify the maximum size of for the cache in
     * bytes, and the maximum lifetime of objects.
     *
     * @param maxSize the maximum size of the cache in bytes.
     * @param maxLifetime the maximum amount of time objects can exist in
     *    cache before being deleted.
     * @param aCacheDir absolute path name of cache directory
     */
    public FileCache(int maxSize, long maxLifetime, String aCacheDir) {
        this(aCacheDir);
        this.maxSize = maxSize;
        this.maxLifetime = maxLifetime;
        KFMSystem.log.info("FileCache::FileCache(): maxSize=" + maxSize + ", maxLifetime=" + maxLifetime + ", aCacheDir=" + aCacheDir);
    }

    private void createOrReuseCacheDirectory(String aCacheDir) {

        this.mCacheDir = aCacheDir;

        File tDir = new File(mCacheDir);
        if (tDir.exists()) {
            // assume old files in cache come from previous run, reuse them

            // The current maxSize value, which was set in base class,
            // is probably to small (as it will be set later),
            // maxSize is temporarily set to MAX_VALUE.
            // Otherwise files will be deleted by add().
            int tMaxSizeTmp = this.maxSize; // default from base class (128 K)
            this.maxSize = Integer.MAX_VALUE;

            String[] tFiles = tDir.list();
            List tFileList = new ArrayList();

            for (int i=0; i < tFiles.length; i++) {
                File tInputFile = new File(tDir, tFiles[i]);
                tFileList.add(tInputFile);
            }

            Collections.sort(tFileList, new FileComparator());

            KFMSystem.log.info("FileCache::createOrReuseCacheDirectory(): reuse "
                         + tFileList.size() + " files");

            for (int i = 0; i < tFileList.size(); i++) {
                File tInputFile = (File) tFileList.get(i);
                String tFilename = tInputFile.getAbsolutePath();

                String tKey = null;
                try {
                    tKey = Converter.decode(tInputFile.getName());
                } catch (Exception e) {
                    throw new ProgrammerException("FileCache::createOrReuseCacheDirectory(): "
                                                  + "Exception in Converter.decode()");
                }

                KFMSystem.log.info("FileCache::createOrReuseCacheDirectory(): reuse file from cache: " + tInputFile);

                add(tKey, new CachedHTMLItem(null, // do not read contents of existing file
                                             tKey),
                    tInputFile.lastModified(),
                    true                           // do reuse contents from file
                    );
            }

            cacheMisses = 0;
            cacheHits = 0;

            this.maxSize = tMaxSizeTmp;
        } else {
            if (! tDir.mkdirs()) {
                KFMSystem.log.error("FileCache::createOrReuseCacheDirectory(): could not create "
                                    + "cache directory " + mCacheDir);
            }
        }
    }


    /**
     * Adds a new Cacheable object to the cache. The key must be unique.
     *
     * @param key a unique key for the object being put into cache.
     * @param object the Cacheable object to put into cache.
     */
    public synchronized void add(Object key, Cacheable object) {
        add(key, object, System.currentTimeMillis(), false);
    }

    /**
     * Adds a new Cacheable object to the cache. The key must be unique.
     *
     * @param key a unique key for the object being put into cache.
     * @param object the Cacheable object to put into cache.
     * @param aTimestamp arbitrary timestamp
     * @param aReuseExistingFile reuses an existing file in cache when <code>true</code>,
     *          writes object to cache in any case, else
     */
    private synchronized void add(Object key, Cacheable object,
                                  long aTimestamp, boolean aReuseExistingFile) {
        //Don't add an object with the same key multiple times.
        if (cachedObjectsHash.containsKey(key)) {
            return;
        }

        int objectSize;

        // Create filename for cached object
        String tFilename = mCacheDir + File.separatorChar + Converter.quoteURL( (String) key);
        File tFile = new File(tFilename);

        if (! aReuseExistingFile) {
            objectSize = object.getSize();
        } else {
            // Note: We have to take care of two special cases here:
            //   The file does not exist (it may have been deleted in between)
            //   The file has a length of 0 bytes (we had that problem once)
            if (tFile.exists() && tFile.length() > 0) {
                objectSize = (int) tFile.length(); // cast is ok: we don't intend to cache files over 2GB
            }
            else {
                // Nothing to do.
                return;
            }
        }

        //If the object is bigger than half of the entire cache, simply don't add it.
        if (objectSize > maxSize * .50) {
            return;
        }

        if (! aReuseExistingFile) {
            // store cache object to file named 'key' (URL encoded)
            String tContent = ((CachedHTMLItem) object).getHTML();
            try {
                writeWholeTextFile(tFilename, tContent);
            } catch (IOException e) {
                KFMSystem.log.info("FileCache::add(): failed to add object: " + e.getMessage());
                File tDir = new File(mCacheDir);
                if (!tDir.exists()) {
                    if (! tDir.mkdirs()) {
                        KFMSystem.log.error("FileCache::add(): could not re-create "
                                            + "cache directory " + mCacheDir);
                    }
                }
                return;
            }
        }

        // Create object to be cached in memory.
        // done with intention: store object on filesystem, not in memory: (sic)
        CacheObject cacheObject = new CacheObject(new CachedHTMLItem(null, // don't store html in memory,
                                                                        (String) key), objectSize);

        size += objectSize;
        cachedObjectsHash.put(key, cacheObject);

        //Make an entry into the cache order list.
        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
        //Store the cache order list entry so that we can get back to it
        //during later lookups.
        cacheObject.lastAccessedListNode = lastAccessedNode;
        //Add the object to the age list
        LinkedListNode ageNode = ageList.addFirst(key);
        //We make an explicit call to currentTimeMillis() so that total accuracy
        //of lifetime calculations is better than one second.
        ageNode.timestamp = aTimestamp;
        cacheObject.ageListNode = ageNode;

        //If cache is too full, remove least used cache entries until it is
        //not too full.
        cullCache();
    }

    /**
     * Gets an object from cache. This method will return null under two
     * conditions:<ul>
     *    <li>The object referenced by the key was never added to cache.
     *    <li>The object referenced by the key has expired from cache.</ul>
     *
     * @param key the unique key of the object to get.
     * @return the Cacheable object corresponding to unique key.
     */
    public synchronized Cacheable get(Object key) {
        //First, clear all entries that have been in cache longer than the
        //maximum defined age.
        deleteExpiredEntries();

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
        if (cacheObject == null) {
            KFMSystem.log.info("FileCache::get(): key not found in cache: " + key); // DEBUG

            //The object didn't exist in cache, so increment cache misses.
            cacheMisses++;
            return null;
        }

        //The object exists in cache, so increment cache hits.
        cacheHits++;

        //Remove the object from it's current place in the cache order list,
        //and re-insert it at the front of the list.
        cacheObject.lastAccessedListNode.remove();
        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

        // load file named key (URL encoded)
        String tFilename = mCacheDir + File.separatorChar
            + Converter.quoteURL( (String) key);
        String tContent;
        try {
            KFMSystem.log.info("FileCache::get(): loading key from cache: " + key);
            tContent = FileUtils.readWholeTextFile(tFilename);
        } catch (IOException e) {
            // failed to load object, somebody deleted the cache manually
            KFMSystem.log.info("FileCache::get(): file was deleted manually: " + e.getMessage());
            // update internal structures
            remove(key);
            cacheMisses++;
            cacheHits--;
            return null;
        }

        return new CachedHTMLItem(tContent, (String) key);
    }

    /**
     * Removes an object from cache.
     *
     * @param key the unique key of the object to remove.
     */
    public synchronized void remove(Object key) {
        KFMSystem.log.info("FileCache::remove(): removing key: " + key );

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
        //If the object is not in cache, stop trying to remove it.
        if (cacheObject == null) {
            return;
        }
        //remove from the hash map
        cachedObjectsHash.remove(key);

        //remove file named key
        File file = new File(mCacheDir + File.separatorChar
                             + Converter.quoteURL( (String) key));
        if (! file.delete()) {
            // failed to delete object, somebody deleted the cached or the cache directory manually
            KFMSystem.log.info("FileCache::remove(): tried removing manually deleted file");
            File tDir = new File(mCacheDir);
            if (!tDir.exists()) {
                if (! tDir.mkdirs()) {
                    KFMSystem.log.error("FileCache::remove(): could not re-create "
                                        + "cache directory " + mCacheDir);
                }
            }
        }

        //remove from the cache order list
        cacheObject.lastAccessedListNode.remove();
        cacheObject.ageListNode.remove();
        //remove references to linked list nodes
        cacheObject.ageListNode = null;
        cacheObject.lastAccessedListNode = null;
        //removed the object, so subtract its size from the total.
        size -= cacheObject.size;
    }

    /**
     * Sortes File's according to their last modified date. (Oldest file first.)
     */
    private static class FileComparator implements Comparator {

        public int compare(Object o1, Object o2) {
            File file1 = (File) o1;
            File file2 = (File) o2;

            if (file1.lastModified() < file2.lastModified()) {
                return -1;
            } else if (file1.lastModified() > file2.lastModified()) {
                return 1;
            } else {
                return 0;
            }

        }
    }

    // Note: candidate for class FileUtils
    //
    // throws IOException in case we couldn't create, write or close the file
    protected static void writeWholeTextFile(String aFilename, String aContent) throws IOException
    {
        File tOutputFile = new File(aFilename);
        try {
            FileWriter tOut = new FileWriter(tOutputFile);
            tOut.write(aContent);
            tOut.close();
        } catch (IOException ex)  {
            // We could either not create, write or close the file,
            // so we rethrow the exception. To be clean, however, we try to remove it first.
            tOutputFile.delete();   // Ignore the result of delete() indicating success/failure
            throw ex;
        }
    }
}
TOP

Related Classes of KFM.Cache.FileCache$FileComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.