0001: /*
0002: * Copyright (c) 2002-2003 by OpenSymphony
0003: * All rights reserved.
0004: */
0005: package com.opensymphony.oscache.base;
0006:
0007: import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
0008: import com.opensymphony.oscache.base.algorithm.LRUCache;
0009: import com.opensymphony.oscache.base.algorithm.UnlimitedCache;
0010: import com.opensymphony.oscache.base.events.*;
0011: import com.opensymphony.oscache.base.persistence.PersistenceListener;
0012: import com.opensymphony.oscache.util.FastCronParser;
0013:
0014: import org.apache.commons.logging.Log;
0015: import org.apache.commons.logging.LogFactory;
0016:
0017: import java.io.Serializable;
0018:
0019: import java.text.ParseException;
0020:
0021: import java.util.*;
0022:
0023: import javax.swing.event.EventListenerList;
0024:
0025: /**
0026: * Provides an interface to the cache itself. Creating an instance of this class
0027: * will create a cache that behaves according to its construction parameters.
0028: * The public API provides methods to manage objects in the cache and configure
0029: * any cache event listeners.
0030: *
0031: * @version $Revision: 468 $
0032: * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
0033: * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
0034: * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
0035: * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
0036: */
0037: public class Cache implements Serializable {
0038: /**
0039: * An event that origininated from within another event.
0040: */
0041: public static final String NESTED_EVENT = "NESTED";
0042: private static transient final Log log = LogFactory
0043: .getLog(Cache.class);
0044:
0045: /**
0046: * A list of all registered event listeners for this cache.
0047: */
0048: protected EventListenerList listenerList = new EventListenerList();
0049:
0050: /**
0051: * The actual cache map. This is where the cached objects are held.
0052: */
0053: private AbstractConcurrentReadCache cacheMap = null;
0054:
0055: /**
0056: * Date of last complete cache flush.
0057: */
0058: private Date flushDateTime = null;
0059:
0060: /**
0061: * A map that holds keys of cache entries that are currently being built, and EntryUpdateState instance as values. This is used to coordinate threads
0062: * that modify/access a same key in concurrence.
0063: *
0064: * The cache checks against this map when a stale entry is requested, or a cache miss is observed.
0065: *
0066: * If the requested key is in here, we know the entry is currently being
0067: * built by another thread and hence we can either block and wait or serve
0068: * the stale entry (depending on whether cache blocking is enabled or not).
0069: * <p>
0070: * To avoid data races, values in this map should remain present during the whole time distinct threads deal with the
0071: * same key. We implement this using explicit reference counting in the EntryUpdateState instance, to be able to clean up
0072: * the map once all threads have declared they are done accessing/updating a given key.
0073: *
0074: * It is not possible to locate this into the CacheEntry because this would require to have a CacheEntry instance for all cache misses, and
0075: * may therefore generate a memory leak. More over, the CacheEntry instance may not be hold in memory in the case no
0076: * memory cache is configured.
0077: */
0078: private Map updateStates = new HashMap();
0079:
0080: /**
0081: * Indicates whether the cache blocks requests until new content has
0082: * been generated or just serves stale content instead.
0083: */
0084: private boolean blocking = false;
0085:
0086: /**
0087: * Create a new Cache
0088: *
0089: * @param useMemoryCaching Specify if the memory caching is going to be used
0090: * @param unlimitedDiskCache Specify if the disk caching is unlimited
0091: * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
0092: */
0093: public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache,
0094: boolean overflowPersistence) {
0095: this (useMemoryCaching, unlimitedDiskCache, overflowPersistence,
0096: false, null, 0);
0097: }
0098:
0099: /**
0100: * Create a new Cache.
0101: *
0102: * If a valid algorithm class is specified, it will be used for this cache.
0103: * Otherwise if a capacity is specified, it will use LRUCache.
0104: * If no algorithm or capacity is specified UnlimitedCache is used.
0105: *
0106: * @see com.opensymphony.oscache.base.algorithm.LRUCache
0107: * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
0108: * @param useMemoryCaching Specify if the memory caching is going to be used
0109: * @param unlimitedDiskCache Specify if the disk caching is unlimited
0110: * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
0111: * @param blocking This parameter takes effect when a cache entry has
0112: * just expired and several simultaneous requests try to retrieve it. While
0113: * one request is rebuilding the content, the other requests will either
0114: * block and wait for the new content (<code>blocking == true</code>) or
0115: * instead receive a copy of the stale content so they don't have to wait
0116: * (<code>blocking == false</code>). the default is <code>false</code>,
0117: * which provides better performance but at the expense of slightly stale
0118: * data being served.
0119: * @param algorithmClass The class implementing the desired algorithm
0120: * @param capacity The capacity
0121: */
0122: public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache,
0123: boolean overflowPersistence, boolean blocking,
0124: String algorithmClass, int capacity) {
0125: // Instantiate the algo class if valid
0126: if (((algorithmClass != null) && (algorithmClass.length() > 0))
0127: && (capacity > 0)) {
0128: try {
0129: cacheMap = (AbstractConcurrentReadCache) Class.forName(
0130: algorithmClass).newInstance();
0131: cacheMap.setMaxEntries(capacity);
0132: } catch (Exception e) {
0133: log
0134: .error("Invalid class name for cache algorithm class. "
0135: + e.toString());
0136: }
0137: }
0138:
0139: if (cacheMap == null) {
0140: // If we have a capacity, use LRU cache otherwise use unlimited Cache
0141: if (capacity > 0) {
0142: cacheMap = new LRUCache(capacity);
0143: } else {
0144: cacheMap = new UnlimitedCache();
0145: }
0146: }
0147:
0148: cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
0149: cacheMap.setOverflowPersistence(overflowPersistence);
0150: cacheMap.setMemoryCaching(useMemoryCaching);
0151:
0152: this .blocking = blocking;
0153: }
0154:
0155: /**
0156: * @return the maximum number of items to cache can hold.
0157: */
0158: public int getCapacity() {
0159: return cacheMap.getMaxEntries();
0160: }
0161:
0162: /**
0163: * Allows the capacity of the cache to be altered dynamically. Note that
0164: * some cache implementations may choose to ignore this setting (eg the
0165: * {@link UnlimitedCache} ignores this call).
0166: *
0167: * @param capacity the maximum number of items to hold in the cache.
0168: */
0169: public void setCapacity(int capacity) {
0170: cacheMap.setMaxEntries(capacity);
0171: }
0172:
0173: /**
0174: * Checks if the cache was flushed more recently than the CacheEntry provided.
0175: * Used to determine whether to refresh the particular CacheEntry.
0176: *
0177: * @param cacheEntry The cache entry which we're seeing whether to refresh
0178: * @return Whether or not the cache has been flushed more recently than this cache entry was updated.
0179: */
0180: public boolean isFlushed(CacheEntry cacheEntry) {
0181: if (flushDateTime != null) {
0182: final long lastUpdate = cacheEntry.getLastUpdate();
0183: final long flushTime = flushDateTime.getTime();
0184:
0185: // CACHE-241: check flushDateTime with current time also
0186: return (flushTime <= System.currentTimeMillis())
0187: && (flushTime >= lastUpdate);
0188: } else {
0189: return false;
0190: }
0191: }
0192:
0193: /**
0194: * Retrieve an object from the cache specifying its key.
0195: *
0196: * @param key Key of the object in the cache.
0197: *
0198: * @return The object from cache
0199: *
0200: * @throws NeedsRefreshException Thrown when the object either
0201: * doesn't exist, or exists but is stale. When this exception occurs,
0202: * the CacheEntry corresponding to the supplied key will be locked
0203: * and other threads requesting this entry will potentially be blocked
0204: * until the caller repopulates the cache. If the caller choses not
0205: * to repopulate the cache, they <em>must</em> instead call
0206: * {@link #cancelUpdate(String)}.
0207: */
0208: public Object getFromCache(String key) throws NeedsRefreshException {
0209: return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
0210: }
0211:
0212: /**
0213: * Retrieve an object from the cache specifying its key.
0214: *
0215: * @param key Key of the object in the cache.
0216: * @param refreshPeriod How long before the object needs refresh. To
0217: * allow the object to stay in the cache indefinitely, supply a value
0218: * of {@link CacheEntry#INDEFINITE_EXPIRY}.
0219: *
0220: * @return The object from cache
0221: *
0222: * @throws NeedsRefreshException Thrown when the object either
0223: * doesn't exist, or exists but is stale. When this exception occurs,
0224: * the CacheEntry corresponding to the supplied key will be locked
0225: * and other threads requesting this entry will potentially be blocked
0226: * until the caller repopulates the cache. If the caller choses not
0227: * to repopulate the cache, they <em>must</em> instead call
0228: * {@link #cancelUpdate(String)}.
0229: */
0230: public Object getFromCache(String key, int refreshPeriod)
0231: throws NeedsRefreshException {
0232: return getFromCache(key, refreshPeriod, null);
0233: }
0234:
0235: /**
0236: * Retrieve an object from the cache specifying its key.
0237: *
0238: * @param key Key of the object in the cache.
0239: * @param refreshPeriod How long before the object needs refresh. To
0240: * allow the object to stay in the cache indefinitely, supply a value
0241: * of {@link CacheEntry#INDEFINITE_EXPIRY}.
0242: * @param cronExpiry A cron expression that specifies fixed date(s)
0243: * and/or time(s) that this cache entry should
0244: * expire on.
0245: *
0246: * @return The object from cache
0247: *
0248: * @throws NeedsRefreshException Thrown when the object either
0249: * doesn't exist, or exists but is stale. When this exception occurs,
0250: * the CacheEntry corresponding to the supplied key will be locked
0251: * and other threads requesting this entry will potentially be blocked
0252: * until the caller repopulates the cache. If the caller choses not
0253: * to repopulate the cache, they <em>must</em> instead call
0254: * {@link #cancelUpdate(String)}.
0255: */
0256: public Object getFromCache(String key, int refreshPeriod,
0257: String cronExpiry) throws NeedsRefreshException {
0258: CacheEntry cacheEntry = this .getCacheEntry(key, null, null);
0259:
0260: Object content = cacheEntry.getContent();
0261: CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
0262:
0263: boolean reload = false;
0264:
0265: // Check if this entry has expired or has not yet been added to the cache. If
0266: // so, we need to decide whether to block, serve stale content or throw a
0267: // NeedsRefreshException
0268: if (this .isStale(cacheEntry, refreshPeriod, cronExpiry)) {
0269:
0270: //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
0271: EntryUpdateState updateState = getUpdateState(key);
0272: try {
0273: synchronized (updateState) {
0274: if (updateState.isAwaitingUpdate()
0275: || updateState.isCancelled()) {
0276: // No one else is currently updating this entry - grab ownership
0277: updateState.startUpdate();
0278:
0279: if (cacheEntry.isNew()) {
0280: accessEventType = CacheMapAccessEventType.MISS;
0281: } else {
0282: accessEventType = CacheMapAccessEventType.STALE_HIT;
0283: }
0284: } else if (updateState.isUpdating()) {
0285: // Another thread is already updating the cache. We block if this
0286: // is a new entry, or blocking mode is enabled. Either putInCache()
0287: // or cancelUpdate() can cause this thread to resume.
0288: if (cacheEntry.isNew() || blocking) {
0289: do {
0290: try {
0291: updateState.wait();
0292: } catch (InterruptedException e) {
0293: }
0294: } while (updateState.isUpdating());
0295:
0296: if (updateState.isCancelled()) {
0297: // The updating thread cancelled the update, let this one have a go.
0298: // This increments the usage count for this EntryUpdateState instance
0299: updateState.startUpdate();
0300:
0301: if (cacheEntry.isNew()) {
0302: accessEventType = CacheMapAccessEventType.MISS;
0303: } else {
0304: accessEventType = CacheMapAccessEventType.STALE_HIT;
0305: }
0306: } else if (updateState.isComplete()) {
0307: reload = true;
0308: } else {
0309: log
0310: .error("Invalid update state for cache entry "
0311: + key);
0312: }
0313: }
0314: } else {
0315: reload = true;
0316: }
0317: }
0318: } finally {
0319: //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
0320: //increased by one in startUpdate()
0321: releaseUpdateState(updateState, key);
0322: }
0323: }
0324:
0325: // If reload is true then another thread must have successfully rebuilt the cache entry
0326: if (reload) {
0327: cacheEntry = (CacheEntry) cacheMap.get(key);
0328:
0329: if (cacheEntry != null) {
0330: content = cacheEntry.getContent();
0331: } else {
0332: log
0333: .error("Could not reload cache entry after waiting for it to be rebuilt");
0334: }
0335: }
0336:
0337: dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
0338:
0339: // If we didn't end up getting a hit then we need to throw a NRE
0340: if (accessEventType != CacheMapAccessEventType.HIT) {
0341: throw new NeedsRefreshException(content);
0342: }
0343:
0344: return content;
0345: }
0346:
0347: /**
0348: * Set the listener to use for data persistence. Only one
0349: * <code>PersistenceListener</code> can be configured per cache.
0350: *
0351: * @param listener The implementation of a persistance listener
0352: */
0353: public void setPersistenceListener(PersistenceListener listener) {
0354: cacheMap.setPersistenceListener(listener);
0355: }
0356:
0357: /**
0358: * Retrieves the currently configured <code>PersistenceListener</code>.
0359: *
0360: * @return the cache's <code>PersistenceListener</code>, or <code>null</code>
0361: * if no listener is configured.
0362: */
0363: public PersistenceListener getPersistenceListener() {
0364: return cacheMap.getPersistenceListener();
0365: }
0366:
0367: /**
0368: * Register a listener for Cache events. The listener must implement
0369: * one of the child interfaces of the {@link CacheEventListener} interface.
0370: *
0371: * @param listener The object that listens to events.
0372: * @since 2.4
0373: */
0374: public void addCacheEventListener(CacheEventListener listener) {
0375: // listenerList.add(CacheEventListener.class, listener);
0376: listenerList.add(listener.getClass(), listener);
0377: }
0378:
0379: /**
0380: * Register a listener for Cache events. The listener must implement
0381: * one of the child interfaces of the {@link CacheEventListener} interface.
0382: *
0383: * @param listener The object that listens to events.
0384: * @param clazz the type of the listener to be added
0385: * @deprecated use {@link #addCacheEventListener(CacheEventListener)}
0386: */
0387: public void addCacheEventListener(CacheEventListener listener,
0388: Class clazz) {
0389: if (CacheEventListener.class.isAssignableFrom(clazz)) {
0390: listenerList.add(clazz, listener);
0391: } else {
0392: log
0393: .error("The class '"
0394: + clazz.getName()
0395: + "' is not a CacheEventListener. Ignoring this listener.");
0396: }
0397: }
0398:
0399: /**
0400: * Returns the list of all CacheEventListeners.
0401: * @return the CacheEventListener's list of the Cache
0402: */
0403: public EventListenerList getCacheEventListenerList() {
0404: return listenerList;
0405: }
0406:
0407: /**
0408: * Cancels any pending update for this cache entry. This should <em>only</em>
0409: * be called by the thread that is responsible for performing the update ie
0410: * the thread that received the original {@link NeedsRefreshException}.<p/>
0411: * If a cache entry is not updated (via {@link #putInCache} and this method is
0412: * not called to let OSCache know the update will not be forthcoming, subsequent
0413: * requests for this cache entry will either block indefinitely (if this is a new
0414: * cache entry or cache.blocking=true), or forever get served stale content. Note
0415: * however that there is no harm in cancelling an update on a key that either
0416: * does not exist or is not currently being updated.
0417: *
0418: * @param key The key for the cache entry in question.
0419: * @throws IllegalStateException if the cache entry isn't in the state UPDATE_IN_PROGRESS
0420: */
0421: public void cancelUpdate(String key) {
0422: EntryUpdateState state;
0423:
0424: if (key != null) {
0425: synchronized (updateStates) {
0426: state = (EntryUpdateState) updateStates.get(key);
0427:
0428: if (state != null) {
0429: synchronized (state) {
0430: int usageCounter = state.cancelUpdate();
0431: state.notify();
0432:
0433: checkEntryStateUpdateUsage(key, state,
0434: usageCounter);
0435: }
0436: } else {
0437: if (log.isErrorEnabled()) {
0438: log
0439: .error("internal error: expected to get a state from key ["
0440: + key + "]");
0441: }
0442: }
0443: }
0444: }
0445: }
0446:
0447: /**
0448: * Utility method to check if the specified usage count is zero, and if so remove the corresponding EntryUpdateState from the updateStates. This is designed to factor common code.
0449: *
0450: * Warning: This method should always be called while holding both the updateStates field and the state parameter
0451: * @throws Exception
0452: */
0453: private void checkEntryStateUpdateUsage(String key,
0454: EntryUpdateState state, int usageCounter) {
0455: //Clean up the updateStates map to avoid a memory leak once no thread is using this EntryUpdateState instance anymore.
0456: if (usageCounter == 0) {
0457: EntryUpdateState removedState = (EntryUpdateState) updateStates
0458: .remove(key);
0459: if (state != removedState) {
0460: if (log.isErrorEnabled()) {
0461: try {
0462: throw new Exception(
0463: "OSCache: internal error: removed state ["
0464: + removedState + "] from key ["
0465: + key
0466: + "] whereas we expected ["
0467: + state + "]");
0468: } catch (Exception e) {
0469: log.error(e);
0470: }
0471: }
0472: }
0473: }
0474: }
0475:
0476: /**
0477: * Flush all entries in the cache on the given date/time.
0478: *
0479: * @param date The date at which all cache entries will be flushed.
0480: */
0481: public void flushAll(Date date) {
0482: flushAll(date, null);
0483: }
0484:
0485: /**
0486: * Flush all entries in the cache on the given date/time.
0487: *
0488: * @param date The date at which all cache entries will be flushed.
0489: * @param origin The origin of this flush request (optional)
0490: */
0491: public void flushAll(Date date, String origin) {
0492: flushDateTime = date;
0493:
0494: if (listenerList.getListenerCount() > 0) {
0495: dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED,
0496: date, origin);
0497: }
0498: }
0499:
0500: /**
0501: * Flush the cache entry (if any) that corresponds to the cache key supplied.
0502: * This call will flush the entry from the cache and remove the references to
0503: * it from any cache groups that it is a member of. On completion of the flush,
0504: * a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
0505: *
0506: * @param key The key of the entry to flush
0507: */
0508: public void flushEntry(String key) {
0509: flushEntry(key, null);
0510: }
0511:
0512: /**
0513: * Flush the cache entry (if any) that corresponds to the cache key supplied.
0514: * This call will mark the cache entry as flushed so that the next access
0515: * to it will cause a {@link NeedsRefreshException}. On completion of the
0516: * flush, a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
0517: *
0518: * @param key The key of the entry to flush
0519: * @param origin The origin of this flush request (optional)
0520: */
0521: public void flushEntry(String key, String origin) {
0522: flushEntry(getCacheEntry(key, null, origin), origin);
0523: }
0524:
0525: /**
0526: * Flushes all objects that belong to the supplied group. On completion
0527: * this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt> event.
0528: *
0529: * @param group The group to flush
0530: */
0531: public void flushGroup(String group) {
0532: flushGroup(group, null);
0533: }
0534:
0535: /**
0536: * Flushes all unexpired objects that belong to the supplied group. On
0537: * completion this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt>
0538: * event.
0539: *
0540: * @param group The group to flush
0541: * @param origin The origin of this flush event (optional)
0542: */
0543: public void flushGroup(String group, String origin) {
0544: // Flush all objects in the group
0545: Set groupEntries = cacheMap.getGroup(group);
0546:
0547: if (groupEntries != null) {
0548: Iterator itr = groupEntries.iterator();
0549: String key;
0550: CacheEntry entry;
0551:
0552: while (itr.hasNext()) {
0553: key = (String) itr.next();
0554: entry = (CacheEntry) cacheMap.get(key);
0555:
0556: if ((entry != null)
0557: && !entry
0558: .needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
0559: flushEntry(entry, NESTED_EVENT);
0560: }
0561: }
0562: }
0563:
0564: if (listenerList.getListenerCount() > 0) {
0565: dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED,
0566: group, origin);
0567: }
0568: }
0569:
0570: /**
0571: * Flush all entries with keys that match a given pattern
0572: *
0573: * @param pattern The key must contain this given value
0574: * @deprecated For performance and flexibility reasons it is preferable to
0575: * store cache entries in groups and use the {@link #flushGroup(String)} method
0576: * instead of relying on pattern flushing.
0577: */
0578: public void flushPattern(String pattern) {
0579: flushPattern(pattern, null);
0580: }
0581:
0582: /**
0583: * Flush all entries with keys that match a given pattern
0584: *
0585: * @param pattern The key must contain this given value
0586: * @param origin The origin of this flush request
0587: * @deprecated For performance and flexibility reasons it is preferable to
0588: * store cache entries in groups and use the {@link #flushGroup(String, String)}
0589: * method instead of relying on pattern flushing.
0590: */
0591: public void flushPattern(String pattern, String origin) {
0592: // Check the pattern
0593: if ((pattern != null) && (pattern.length() > 0)) {
0594: String key = null;
0595: CacheEntry entry = null;
0596: Iterator itr = cacheMap.keySet().iterator();
0597:
0598: while (itr.hasNext()) {
0599: key = (String) itr.next();
0600:
0601: if (key.indexOf(pattern) >= 0) {
0602: entry = (CacheEntry) cacheMap.get(key);
0603:
0604: if (entry != null) {
0605: flushEntry(entry, origin);
0606: }
0607: }
0608: }
0609:
0610: if (listenerList.getListenerCount() > 0) {
0611: dispatchCachePatternEvent(
0612: CacheEntryEventType.PATTERN_FLUSHED, pattern,
0613: origin);
0614: }
0615: } else {
0616: // Empty pattern, nothing to do
0617: }
0618: }
0619:
0620: /**
0621: * Put an object in the cache specifying the key to use.
0622: *
0623: * @param key Key of the object in the cache.
0624: * @param content The object to cache.
0625: */
0626: public void putInCache(String key, Object content) {
0627: putInCache(key, content, null, null, null);
0628: }
0629:
0630: /**
0631: * Put an object in the cache specifying the key and refresh policy to use.
0632: *
0633: * @param key Key of the object in the cache.
0634: * @param content The object to cache.
0635: * @param policy Object that implements refresh policy logic
0636: */
0637: public void putInCache(String key, Object content,
0638: EntryRefreshPolicy policy) {
0639: putInCache(key, content, null, policy, null);
0640: }
0641:
0642: /**
0643: * Put in object into the cache, specifying both the key to use and the
0644: * cache groups the object belongs to.
0645: *
0646: * @param key Key of the object in the cache
0647: * @param content The object to cache
0648: * @param groups The cache groups to add the object to
0649: */
0650: public void putInCache(String key, Object content, String[] groups) {
0651: putInCache(key, content, groups, null, null);
0652: }
0653:
0654: /**
0655: * Put an object into the cache specifying both the key to use and the
0656: * cache groups the object belongs to.
0657: *
0658: * @param key Key of the object in the cache
0659: * @param groups The cache groups to add the object to
0660: * @param content The object to cache
0661: * @param policy Object that implements the refresh policy logic
0662: */
0663: public void putInCache(String key, Object content, String[] groups,
0664: EntryRefreshPolicy policy, String origin) {
0665: CacheEntry cacheEntry = this .getCacheEntry(key, policy, origin);
0666: boolean isNewEntry = cacheEntry.isNew();
0667:
0668: // [CACHE-118] If we have an existing entry, create a new CacheEntry so we can still access the old one later
0669: if (!isNewEntry) {
0670: cacheEntry = new CacheEntry(key, policy);
0671: }
0672:
0673: cacheEntry.setContent(content);
0674: cacheEntry.setGroups(groups);
0675: cacheMap.put(key, cacheEntry);
0676:
0677: // Signal to any threads waiting on this update that it's now ready for them
0678: // in the cache!
0679: completeUpdate(key);
0680:
0681: if (listenerList.getListenerCount() > 0) {
0682: CacheEntryEvent event = new CacheEntryEvent(this ,
0683: cacheEntry, origin);
0684:
0685: if (isNewEntry) {
0686: dispatchCacheEntryEvent(
0687: CacheEntryEventType.ENTRY_ADDED, event);
0688: } else {
0689: dispatchCacheEntryEvent(
0690: CacheEntryEventType.ENTRY_UPDATED, event);
0691: }
0692: }
0693: }
0694:
0695: /**
0696: * Unregister a listener for Cache events.
0697: *
0698: * @param listener The object that currently listens to events.
0699: * @param clazz The registrated class of listening object.
0700: * @deprecated use instead {@link #removeCacheEventListener(CacheEventListener)}
0701: */
0702: public void removeCacheEventListener(CacheEventListener listener,
0703: Class clazz) {
0704: listenerList.remove(clazz, listener);
0705: }
0706:
0707: /**
0708: * Unregister a listener for Cache events.
0709: *
0710: * @param listener The object that currently listens to events.
0711: * @since 2.4
0712: */
0713: public void removeCacheEventListener(CacheEventListener listener) {
0714: // listenerList.remove(CacheEventListener.class, listener);
0715: listenerList.remove(listener.getClass(), listener);
0716: }
0717:
0718: /**
0719: * Get an entry from this cache or create one if it doesn't exist.
0720: *
0721: * @param key The key of the cache entry
0722: * @param policy Object that implements refresh policy logic
0723: * @param origin The origin of request (optional)
0724: * @return CacheEntry for the specified key.
0725: */
0726: protected CacheEntry getCacheEntry(String key,
0727: EntryRefreshPolicy policy, String origin) {
0728: CacheEntry cacheEntry = null;
0729:
0730: // Verify that the key is valid
0731: if ((key == null) || (key.length() == 0)) {
0732: throw new IllegalArgumentException(
0733: "getCacheEntry called with an empty or null key");
0734: }
0735:
0736: cacheEntry = (CacheEntry) cacheMap.get(key);
0737:
0738: // if the cache entry does not exist, create a new one
0739: if (cacheEntry == null) {
0740: if (log.isDebugEnabled()) {
0741: log.debug("No cache entry exists for key='" + key
0742: + "', creating");
0743: }
0744:
0745: cacheEntry = new CacheEntry(key, policy);
0746: }
0747:
0748: return cacheEntry;
0749: }
0750:
0751: /**
0752: * Indicates whether or not the cache entry is stale.
0753: *
0754: * @param cacheEntry The cache entry to test the freshness of.
0755: * @param refreshPeriod The maximum allowable age of the entry, in seconds.
0756: * @param cronExpiry A cron expression specifying absolute date(s) and/or time(s)
0757: * that the cache entry should expire at. If the cache entry was refreshed prior to
0758: * the most recent match for the cron expression, the entry will be considered stale.
0759: *
0760: * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
0761: */
0762: protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod,
0763: String cronExpiry) {
0764: boolean result = cacheEntry.needsRefresh(refreshPeriod)
0765: || isFlushed(cacheEntry);
0766:
0767: if ((!result) && (cronExpiry != null)
0768: && (cronExpiry.length() > 0)) {
0769: try {
0770: FastCronParser parser = new FastCronParser(cronExpiry);
0771: result = result
0772: || parser.hasMoreRecentMatch(cacheEntry
0773: .getLastUpdate());
0774: } catch (ParseException e) {
0775: log.warn(e);
0776: }
0777: }
0778:
0779: return result;
0780: }
0781:
0782: /**
0783: * Get the updating cache entry from the update map. If one is not found,
0784: * create a new one (with state {@link EntryUpdateState#NOT_YET_UPDATING})
0785: * and add it to the map.
0786: *
0787: * @param key The cache key for this entry
0788: *
0789: * @return the CacheEntry that was found (or added to) the updatingEntries
0790: * map.
0791: */
0792: protected EntryUpdateState getUpdateState(String key) {
0793: EntryUpdateState updateState;
0794:
0795: synchronized (updateStates) {
0796: // Try to find the matching state object in the updating entry map.
0797: updateState = (EntryUpdateState) updateStates.get(key);
0798:
0799: if (updateState == null) {
0800: // It's not there so add it.
0801: updateState = new EntryUpdateState();
0802: updateStates.put(key, updateState);
0803: } else {
0804: //Otherwise indicate that we start using it to prevent its removal until all threads are done with it.
0805: updateState.incrementUsageCounter();
0806: }
0807: }
0808:
0809: return updateState;
0810: }
0811:
0812: /**
0813: * releases the usage that was made of the specified EntryUpdateState. When this reaches zero, the entry is removed from the map.
0814: * @param state the state to release the usage of
0815: * @param key the associated key.
0816: */
0817: protected void releaseUpdateState(EntryUpdateState state, String key) {
0818: synchronized (updateStates) {
0819: int usageCounter = state.decrementUsageCounter();
0820: checkEntryStateUpdateUsage(key, state, usageCounter);
0821: }
0822: }
0823:
0824: /**
0825: * Completely clears the cache.
0826: */
0827: protected void clear() {
0828: cacheMap.clear();
0829: }
0830:
0831: /**
0832: * Removes the update state for the specified key and notifies any other
0833: * threads that are waiting on this object. This is called automatically
0834: * by the {@link #putInCache} method, so it is possible that no EntryUpdateState was hold
0835: * when this method is called.
0836: *
0837: * @param key The cache key that is no longer being updated.
0838: */
0839: protected void completeUpdate(String key) {
0840: EntryUpdateState state;
0841:
0842: synchronized (updateStates) {
0843: state = (EntryUpdateState) updateStates.get(key);
0844:
0845: if (state != null) {
0846: synchronized (state) {
0847: int usageCounter = state.completeUpdate();
0848: state.notifyAll();
0849:
0850: checkEntryStateUpdateUsage(key, state, usageCounter);
0851:
0852: }
0853: } else {
0854: //If putInCache() was called directly (i.e. not as a result of a NeedRefreshException) then no EntryUpdateState would be found.
0855: }
0856: }
0857: }
0858:
0859: /**
0860: * Completely removes a cache entry from the cache and its associated cache
0861: * groups.
0862: *
0863: * @param key The key of the entry to remove.
0864: */
0865: public void removeEntry(String key) {
0866: removeEntry(key, null);
0867: }
0868:
0869: /**
0870: * Completely removes a cache entry from the cache and its associated cache
0871: * groups.
0872: *
0873: * @param key The key of the entry to remove.
0874: * @param origin The origin of this remove request.
0875: */
0876: protected void removeEntry(String key, String origin) {
0877: CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
0878: cacheMap.remove(key);
0879:
0880: if (listenerList.getListenerCount() > 0) {
0881: CacheEntryEvent event = new CacheEntryEvent(this ,
0882: cacheEntry, origin);
0883: dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED,
0884: event);
0885: }
0886: }
0887:
0888: /**
0889: * Dispatch a cache entry event to all registered listeners.
0890: *
0891: * @param eventType The type of event (used to branch on the proper method)
0892: * @param event The event that was fired
0893: */
0894: private void dispatchCacheEntryEvent(CacheEntryEventType eventType,
0895: CacheEntryEvent event) {
0896: // Guaranteed to return a non-null array
0897: Object[] listeners = listenerList.getListenerList();
0898:
0899: // Process the listeners last to first, notifying
0900: // those that are interested in this event
0901: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0902: if (listeners[i + 1] instanceof CacheEntryEventListener) {
0903: CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i + 1];
0904: if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
0905: listener.cacheEntryAdded(event);
0906: } else if (eventType
0907: .equals(CacheEntryEventType.ENTRY_UPDATED)) {
0908: listener.cacheEntryUpdated(event);
0909: } else if (eventType
0910: .equals(CacheEntryEventType.ENTRY_FLUSHED)) {
0911: listener.cacheEntryFlushed(event);
0912: } else if (eventType
0913: .equals(CacheEntryEventType.ENTRY_REMOVED)) {
0914: listener.cacheEntryRemoved(event);
0915: }
0916: }
0917: }
0918: }
0919:
0920: /**
0921: * Dispatch a cache group event to all registered listeners.
0922: *
0923: * @param eventType The type of event (this is used to branch to the correct method handler)
0924: * @param group The cache group that the event applies to
0925: * @param origin The origin of this event (optional)
0926: */
0927: private void dispatchCacheGroupEvent(CacheEntryEventType eventType,
0928: String group, String origin) {
0929: CacheGroupEvent event = new CacheGroupEvent(this , group, origin);
0930:
0931: // Guaranteed to return a non-null array
0932: Object[] listeners = listenerList.getListenerList();
0933:
0934: // Process the listeners last to first, notifying
0935: // those that are interested in this event
0936: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0937: if (listeners[i + 1] instanceof CacheEntryEventListener) {
0938: CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i + 1];
0939: if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
0940: listener.cacheGroupFlushed(event);
0941: }
0942: }
0943: }
0944: }
0945:
0946: /**
0947: * Dispatch a cache map access event to all registered listeners.
0948: *
0949: * @param eventType The type of event
0950: * @param entry The entry that was affected.
0951: * @param origin The origin of this event (optional)
0952: */
0953: private void dispatchCacheMapAccessEvent(
0954: CacheMapAccessEventType eventType, CacheEntry entry,
0955: String origin) {
0956: CacheMapAccessEvent event = new CacheMapAccessEvent(eventType,
0957: entry, origin);
0958:
0959: // Guaranteed to return a non-null array
0960: Object[] listeners = listenerList.getListenerList();
0961:
0962: // Process the listeners last to first, notifying
0963: // those that are interested in this event
0964: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0965: if (listeners[i + 1] instanceof CacheMapAccessEventListener) {
0966: CacheMapAccessEventListener listener = (CacheMapAccessEventListener) listeners[i + 1];
0967: listener.accessed(event);
0968: }
0969: }
0970: }
0971:
0972: /**
0973: * Dispatch a cache pattern event to all registered listeners.
0974: *
0975: * @param eventType The type of event (this is used to branch to the correct method handler)
0976: * @param pattern The cache pattern that the event applies to
0977: * @param origin The origin of this event (optional)
0978: */
0979: private void dispatchCachePatternEvent(
0980: CacheEntryEventType eventType, String pattern, String origin) {
0981: CachePatternEvent event = new CachePatternEvent(this , pattern,
0982: origin);
0983:
0984: // Guaranteed to return a non-null array
0985: Object[] listeners = listenerList.getListenerList();
0986:
0987: // Process the listeners last to first, notifying
0988: // those that are interested in this event
0989: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0990: if (listeners[i + 1] instanceof CacheEntryEventListener) {
0991: if (eventType
0992: .equals(CacheEntryEventType.PATTERN_FLUSHED)) {
0993: CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i + 1];
0994: listener.cachePatternFlushed(event);
0995: }
0996: }
0997: }
0998: }
0999:
1000: /**
1001: * Dispatches a cache-wide event to all registered listeners.
1002: *
1003: * @param eventType The type of event (this is used to branch to the correct method handler)
1004: * @param origin The origin of this event (optional)
1005: */
1006: private void dispatchCachewideEvent(CachewideEventType eventType,
1007: Date date, String origin) {
1008: CachewideEvent event = new CachewideEvent(this , date, origin);
1009:
1010: // Guaranteed to return a non-null array
1011: Object[] listeners = listenerList.getListenerList();
1012:
1013: // Process the listeners last to first, notifying
1014: // those that are interested in this event
1015: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1016: if (listeners[i + 1] instanceof CacheEntryEventListener) {
1017: if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
1018: CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i + 1];
1019: listener.cacheFlushed(event);
1020: }
1021: }
1022: }
1023: }
1024:
1025: /**
1026: * Flush a cache entry. On completion of the flush, a
1027: * <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
1028: *
1029: * @param entry The entry to flush
1030: * @param origin The origin of this flush event (optional)
1031: */
1032: private void flushEntry(CacheEntry entry, String origin) {
1033: String key = entry.getKey();
1034:
1035: // Flush the object itself
1036: entry.flush();
1037:
1038: if (!entry.isNew()) {
1039: // Update the entry's state in the map
1040: cacheMap.put(key, entry);
1041: }
1042:
1043: // Trigger an ENTRY_FLUSHED event. [CACHE-107] Do this for all flushes.
1044: if (listenerList.getListenerCount() > 0) {
1045: CacheEntryEvent event = new CacheEntryEvent(this , entry,
1046: origin);
1047: dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED,
1048: event);
1049: }
1050: }
1051:
1052: /**
1053: * @return the total number of cache entries held in this cache.
1054: */
1055: public int getSize() {
1056: synchronized (cacheMap) {
1057: return cacheMap.size();
1058: }
1059: }
1060:
1061: /**
1062: * Test support only: return the number of EntryUpdateState instances within the updateStates map.
1063: */
1064: protected int getNbUpdateState() {
1065: synchronized (updateStates) {
1066: return updateStates.size();
1067: }
1068: }
1069:
1070: /**
1071: * Test support only: return the number of entries currently in the cache map
1072: * @deprecated use getSize()
1073: */
1074: public int getNbEntries() {
1075: synchronized (cacheMap) {
1076: return cacheMap.size();
1077: }
1078: }
1079: }
|