⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cache.java

📁 oscache-2.4.1-full
💻 JAVA
📖 第 1 页 / 共 3 页
字号:
/*
 * Copyright (c) 2002-2003 by OpenSymphony
 * All rights reserved.
 */
package com.opensymphony.oscache.base;

import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
import com.opensymphony.oscache.base.algorithm.LRUCache;
import com.opensymphony.oscache.base.algorithm.UnlimitedCache;
import com.opensymphony.oscache.base.events.*;
import com.opensymphony.oscache.base.persistence.PersistenceListener;
import com.opensymphony.oscache.util.FastCronParser;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;

import java.text.ParseException;

import java.util.*;

import javax.swing.event.EventListenerList;

/**
 * Provides an interface to the cache itself. Creating an instance of this class
 * will create a cache that behaves according to its construction parameters.
 * The public API provides methods to manage objects in the cache and configure
 * any cache event listeners.
 *
 * @version        $Revision: 468 $
 * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
 * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
 */
public class Cache implements Serializable {
    /**
     * An event that origininated from within another event.
     */
    public static final String NESTED_EVENT = "NESTED";
    private static transient final Log log = LogFactory.getLog(Cache.class);

    /**
     * A list of all registered event listeners for this cache.
     */
    protected EventListenerList listenerList = new EventListenerList();

    /**
     * The actual cache map. This is where the cached objects are held.
     */
    private AbstractConcurrentReadCache cacheMap = null;

    /**
     * Date of last complete cache flush.
     */
    private Date flushDateTime = null;

    /**
     * A map that holds keys of cache entries that are currently being built, and EntryUpdateState instance as values. This is used to coordinate threads
     * that modify/access a same key in concurrence.
     * 
     * The cache checks against this map when a stale entry is requested, or a cache miss is observed.
     * 
     * If the requested key is in here, we know the entry is currently being
     * built by another thread and hence we can either block and wait or serve
     * the stale entry (depending on whether cache blocking is enabled or not).
     * <p>
     * To avoid data races, values in this map should remain present during the whole time distinct threads deal with the
     * same key. We implement this using explicit reference counting in the EntryUpdateState instance, to be able to clean up
     * the map once all threads have declared they are done accessing/updating a given key.
     * 
     * It is not possible to locate this into the CacheEntry because this would require to have a CacheEntry instance for all cache misses, and
     * may therefore generate a memory leak. More over, the CacheEntry instance may not be hold in memory in the case no
     * memory cache is configured.
     */
    private Map updateStates = new HashMap();

    /**
     * Indicates whether the cache blocks requests until new content has
     * been generated or just serves stale content instead.
     */
    private boolean blocking = false;

    /**
     * Create a new Cache
     *
     * @param useMemoryCaching Specify if the memory caching is going to be used
     * @param unlimitedDiskCache Specify if the disk caching is unlimited
     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
     */
    public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) {
        this(useMemoryCaching, unlimitedDiskCache, overflowPersistence, false, null, 0);
    }

    /**
     * Create a new Cache.
     *
     * If a valid algorithm class is specified, it will be used for this cache.
     * Otherwise if a capacity is specified, it will use LRUCache.
     * If no algorithm or capacity is specified UnlimitedCache is used.
     *
     * @see com.opensymphony.oscache.base.algorithm.LRUCache
     * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
     * @param useMemoryCaching Specify if the memory caching is going to be used
     * @param unlimitedDiskCache Specify if the disk caching is unlimited
     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
     * @param blocking This parameter takes effect when a cache entry has
     * just expired and several simultaneous requests try to retrieve it. While
     * one request is rebuilding the content, the other requests will either
     * block and wait for the new content (<code>blocking == true</code>) or
     * instead receive a copy of the stale content so they don't have to wait
     * (<code>blocking == false</code>). the default is <code>false</code>,
     * which provides better performance but at the expense of slightly stale
     * data being served.
     * @param algorithmClass The class implementing the desired algorithm
     * @param capacity The capacity
     */
    public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String algorithmClass, int capacity) {
        // Instantiate the algo class if valid
        if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
            try {
                cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
                cacheMap.setMaxEntries(capacity);
            } catch (Exception e) {
                log.error("Invalid class name for cache algorithm class. " + e.toString());
            }
        }

        if (cacheMap == null) {
            // If we have a capacity, use LRU cache otherwise use unlimited Cache
            if (capacity > 0) {
                cacheMap = new LRUCache(capacity);
            } else {
                cacheMap = new UnlimitedCache();
            }
        }

        cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
        cacheMap.setOverflowPersistence(overflowPersistence);
        cacheMap.setMemoryCaching(useMemoryCaching);

        this.blocking = blocking;
    }
    
    /**
     * @return the maximum number of items to cache can hold.
     */
    public int getCapacity() {
        return cacheMap.getMaxEntries();
    }

    /**
     * Allows the capacity of the cache to be altered dynamically. Note that
     * some cache implementations may choose to ignore this setting (eg the
     * {@link UnlimitedCache} ignores this call).
     *
     * @param capacity the maximum number of items to hold in the cache.
     */
    public void setCapacity(int capacity) {
        cacheMap.setMaxEntries(capacity);
    }

    /**
     * Checks if the cache was flushed more recently than the CacheEntry provided.
     * Used to determine whether to refresh the particular CacheEntry.
     *
     * @param cacheEntry The cache entry which we're seeing whether to refresh
     * @return Whether or not the cache has been flushed more recently than this cache entry was updated.
     */
    public boolean isFlushed(CacheEntry cacheEntry) {
        if (flushDateTime != null) {
            final long lastUpdate = cacheEntry.getLastUpdate();
            final long flushTime = flushDateTime.getTime();

            // CACHE-241: check flushDateTime with current time also
            return (flushTime <= System.currentTimeMillis()) && (flushTime >= lastUpdate);
        } else {
            return false;
        }
    }

    /**
     * Retrieve an object from the cache specifying its key.
     *
     * @param key             Key of the object in the cache.
     *
     * @return The object from cache
     *
     * @throws NeedsRefreshException Thrown when the object either
     * doesn't exist, or exists but is stale. When this exception occurs,
     * the CacheEntry corresponding to the supplied key will be locked
     * and other threads requesting this entry will potentially be blocked
     * until the caller repopulates the cache. If the caller choses not
     * to repopulate the cache, they <em>must</em> instead call
     * {@link #cancelUpdate(String)}.
     */
    public Object getFromCache(String key) throws NeedsRefreshException {
        return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
    }

    /**
     * Retrieve an object from the cache specifying its key.
     *
     * @param key             Key of the object in the cache.
     * @param refreshPeriod   How long before the object needs refresh. To
     * allow the object to stay in the cache indefinitely, supply a value
     * of {@link CacheEntry#INDEFINITE_EXPIRY}.
     *
     * @return The object from cache
     *
     * @throws NeedsRefreshException Thrown when the object either
     * doesn't exist, or exists but is stale. When this exception occurs,
     * the CacheEntry corresponding to the supplied key will be locked
     * and other threads requesting this entry will potentially be blocked
     * until the caller repopulates the cache. If the caller choses not
     * to repopulate the cache, they <em>must</em> instead call
     * {@link #cancelUpdate(String)}.
     */
    public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
        return getFromCache(key, refreshPeriod, null);
    }

    /**
     * Retrieve an object from the cache specifying its key.
     *
     * @param key             Key of the object in the cache.
     * @param refreshPeriod   How long before the object needs refresh. To
     * allow the object to stay in the cache indefinitely, supply a value
     * of {@link CacheEntry#INDEFINITE_EXPIRY}.
     * @param cronExpiry      A cron expression that specifies fixed date(s)
     *                        and/or time(s) that this cache entry should
     *                        expire on.
     *
     * @return The object from cache
     *
     * @throws NeedsRefreshException Thrown when the object either
     * doesn't exist, or exists but is stale. When this exception occurs,
     * the CacheEntry corresponding to the supplied key will be locked
     * and other threads requesting this entry will potentially be blocked
     * until the caller repopulates the cache. If the caller choses not
     * to repopulate the cache, they <em>must</em> instead call
     * {@link #cancelUpdate(String)}.
     */
    public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
        CacheEntry cacheEntry = this.getCacheEntry(key, null, null);

        Object content = cacheEntry.getContent();
        CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;

        boolean reload = false;

        // Check if this entry has expired or has not yet been added to the cache. If
        // so, we need to decide whether to block, serve stale content or throw a
        // NeedsRefreshException
        if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {

            //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
            EntryUpdateState updateState = getUpdateState(key);
            try {
                synchronized (updateState) {
                    if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
                        // No one else is currently updating this entry - grab ownership
                        updateState.startUpdate();
                        
                        if (cacheEntry.isNew()) {
                            accessEventType = CacheMapAccessEventType.MISS;
                        } else {
                            accessEventType = CacheMapAccessEventType.STALE_HIT;
                        }
                    } else if (updateState.isUpdating()) {
                        // Another thread is already updating the cache. We block if this
                        // is a new entry, or blocking mode is enabled. Either putInCache()
                        // or cancelUpdate() can cause this thread to resume.
                        if (cacheEntry.isNew() || blocking) {
                            do {
                                try {
                                    updateState.wait();
                                } catch (InterruptedException e) {
                                }
                            } while (updateState.isUpdating());
                            
                            if (updateState.isCancelled()) {
                                // The updating thread cancelled the update, let this one have a go. 
                                // This increments the usage count for this EntryUpdateState instance
                                updateState.startUpdate();
                                
                                if (cacheEntry.isNew()) {
                                    accessEventType = CacheMapAccessEventType.MISS;
                                } else {
                                    accessEventType = CacheMapAccessEventType.STALE_HIT;
                                }
                            } else if (updateState.isComplete()) {
                                reload = true;
                            } else {
                                log.error("Invalid update state for cache entry " + key);
                            }
                        }
                    } else {
                        reload = true;
                    }
                }
            } finally {
                //Make sure we release the usage count for this EntryUpdateState since we don't use it anymore. If the current thread started the update, then the counter was
                //increased by one in startUpdate()
                releaseUpdateState(updateState, key);
            }
        }

        // If reload is true then another thread must have successfully rebuilt the cache entry
        if (reload) {
            cacheEntry = (CacheEntry) cacheMap.get(key);

            if (cacheEntry != null) {
                content = cacheEntry.getContent();
            } else {
                log.error("Could not reload cache entry after waiting for it to be rebuilt");
            }
        }

        dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);

        // If we didn't end up getting a hit then we need to throw a NRE
        if (accessEventType != CacheMapAccessEventType.HIT) {
            throw new NeedsRefreshException(content);
        }

        return content;
    }

    /**
     * Set the listener to use for data persistence. Only one
     * <code>PersistenceListener</code> can be configured per cache.
     *
     * @param listener The implementation of a persistance listener
     */
    public void setPersistenceListener(PersistenceListener listener) {
        cacheMap.setPersistenceListener(listener);
    }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -