Clover coverage report -
Coverage timestamp: Mo Mrz 6 2006 19:30:45 CET
file stats: LOC: 964   Methods: 42
NCLOC: 414   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Cache.java 74,2% 81,3% 78,6% 78,7%
coverage coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    package com.opensymphony.oscache.base;
 6   
 7    import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
 8    import com.opensymphony.oscache.base.algorithm.LRUCache;
 9    import com.opensymphony.oscache.base.algorithm.UnlimitedCache;
 10    import com.opensymphony.oscache.base.events.*;
 11    import com.opensymphony.oscache.base.persistence.PersistenceListener;
 12    import com.opensymphony.oscache.util.FastCronParser;
 13   
 14    import org.apache.commons.logging.Log;
 15    import org.apache.commons.logging.LogFactory;
 16   
 17    import java.io.Serializable;
 18   
 19    import java.text.ParseException;
 20   
 21    import java.util.*;
 22   
 23    import javax.swing.event.EventListenerList;
 24   
 25    /**
 26    * Provides an interface to the cache itself. Creating an instance of this class
 27    * will create a cache that behaves according to its construction parameters.
 28    * The public API provides methods to manage objects in the cache and configure
 29    * any cache event listeners.
 30    *
 31    * @version $Revision: 1.3 $
 32    * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 33    * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
 34    * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 35    * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
 36    */
 37    public class Cache implements Serializable {
 38    /**
 39    * An event that origininated from within another event.
 40    */
 41    public static final String NESTED_EVENT = "NESTED";
 42    private static transient final Log log = LogFactory.getLog(Cache.class);
 43   
 44    /**
 45    * A list of all registered event listeners for this cache.
 46    */
 47    protected EventListenerList listenerList = new EventListenerList();
 48   
 49    /**
 50    * The actual cache map. This is where the cached objects are held.
 51    */
 52    private AbstractConcurrentReadCache cacheMap = null;
 53   
 54    /**
 55    * Date of last complete cache flush.
 56    */
 57    private Date flushDateTime = null;
 58   
 59    /**
 60    * A map that holds keys of cache entries that are currently being built, and EntryUpdateState instance as values. This is used to coordinate threads
 61    * that modify/access a same key in concurrence.
 62    *
 63    * The cache checks against this map when a stale entry is requested, or a cache miss is observed.
 64    *
 65    * If the requested key is in here, we know the entry is currently being
 66    * built by another thread and hence we can either block and wait or serve
 67    * the stale entry (depending on whether cache blocking is enabled or not).
 68    * <p>
 69    * To avoid data races, values in this map should remain present during the whole time distinct threads deal with the
 70    * same key. We implement this using explicit reference counting in the EntryUpdateState instance, to be able to clean up
 71    * the map once all threads have declared they are done accessing/updating a given key.
 72    *
 73    * It is not possible to locate this into the CacheEntry because this would require to have a CacheEntry instance for all cache misses, and
 74    * may therefore generate a memory leak. More over, the CacheEntry instance may not be hold in memory in the case no
 75    * memory cache is configured.
 76    */
 77    private Map updateStates = new HashMap();
 78   
 79    /**
 80    * Indicates whether the cache blocks requests until new content has
 81    * been generated or just serves stale content instead.
 82    */
 83    private boolean blocking = false;
 84   
 85    /**
 86    * Create a new Cache
 87    *
 88    * @param useMemoryCaching Specify if the memory caching is going to be used
 89    * @param unlimitedDiskCache Specify if the disk caching is unlimited
 90    * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
 91    */
 92  12 public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) {
 93  12 this(useMemoryCaching, unlimitedDiskCache, overflowPersistence, false, null, 0);
 94    }
 95   
 96    /**
 97    * Create a new Cache.
 98    *
 99    * If a valid algorithm class is specified, it will be used for this cache.
 100    * Otherwise if a capacity is specified, it will use LRUCache.
 101    * If no algorithm or capacity is specified UnlimitedCache is used.
 102    *
 103    * @see com.opensymphony.oscache.base.algorithm.LRUCache
 104    * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
 105    * @param useMemoryCaching Specify if the memory caching is going to be used
 106    * @param unlimitedDiskCache Specify if the disk caching is unlimited
 107    * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
 108    * @param blocking This parameter takes effect when a cache entry has
 109    * just expired and several simultaneous requests try to retrieve it. While
 110    * one request is rebuilding the content, the other requests will either
 111    * block and wait for the new content (<code>blocking == true</code>) or
 112    * instead receive a copy of the stale content so they don't have to wait
 113    * (<code>blocking == false</code>). the default is <code>false</code>,
 114    * which provides better performance but at the expense of slightly stale
 115    * data being served.
 116    * @param algorithmClass The class implementing the desired algorithm
 117    * @param capacity The capacity
 118    */
 119  108 public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String algorithmClass, int capacity) {
 120    // Instantiate the algo class if valid
 121  108 if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
 122  16 try {
 123  16 cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
 124  16 cacheMap.setMaxEntries(capacity);
 125    } catch (Exception e) {
 126  0 log.error("Invalid class name for cache algorithm class. " + e.toString());
 127    }
 128    }
 129   
 130  108 if (cacheMap == null) {
 131    // If we have a capacity, use LRU cache otherwise use unlimited Cache
 132  92 if (capacity > 0) {
 133  36 cacheMap = new LRUCache(capacity);
 134    } else {
 135  56 cacheMap = new UnlimitedCache();
 136    }
 137    }
 138   
 139  108 cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
 140  108 cacheMap.setOverflowPersistence(overflowPersistence);
 141  108 cacheMap.setMemoryCaching(useMemoryCaching);
 142   
 143  108 this.blocking = blocking;
 144    }
 145   
 146    /**
 147    * Allows the capacity of the cache to be altered dynamically. Note that
 148    * some cache implementations may choose to ignore this setting (eg the
 149    * {@link UnlimitedCache} ignores this call).
 150    *
 151    * @param capacity the maximum number of items to hold in the cache.
 152    */
 153  16 public void setCapacity(int capacity) {
 154  16 cacheMap.setMaxEntries(capacity);
 155    }
 156   
 157    /**
 158    * Checks if the cache was flushed more recently than the CacheEntry provided.
 159    * Used to determine whether to refresh the particular CacheEntry.
 160    *
 161    * @param cacheEntry The cache entry which we're seeing whether to refresh
 162    * @return Whether or not the cache has been flushed more recently than this cache entry was updated.
 163    */
 164  226 public boolean isFlushed(CacheEntry cacheEntry) {
 165  226 if (flushDateTime != null) {
 166  0 long lastUpdate = cacheEntry.getLastUpdate();
 167   
 168  0 return (flushDateTime.getTime() >= lastUpdate);
 169    } else {
 170  226 return false;
 171    }
 172    }
 173   
 174    /**
 175    * Retrieve an object from the cache specifying its key.
 176    *
 177    * @param key Key of the object in the cache.
 178    *
 179    * @return The object from cache
 180    *
 181    * @throws NeedsRefreshException Thrown when the object either
 182    * doesn't exist, or exists but is stale. When this exception occurs,
 183    * the CacheEntry corresponding to the supplied key will be locked
 184    * and other threads requesting this entry will potentially be blocked
 185    * until the caller repopulates the cache. If the caller choses not
 186    * to repopulate the cache, they <em>must</em> instead call
 187    * {@link #cancelUpdate(String)}.
 188    */
 189  8000 public Object getFromCache(String key) throws NeedsRefreshException {
 190  8000 return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
 191    }
 192   
 193    /**
 194    * Retrieve an object from the cache specifying its key.
 195    *
 196    * @param key Key of the object in the cache.
 197    * @param refreshPeriod How long before the object needs refresh. To
 198    * allow the object to stay in the cache indefinitely, supply a value
 199    * of {@link CacheEntry#INDEFINITE_EXPIRY}.
 200    *
 201    * @return The object from cache
 202    *
 203    * @throws NeedsRefreshException Thrown when the object either
 204    * doesn't exist, or exists but is stale. When this exception occurs,
 205    * the CacheEntry corresponding to the supplied key will be locked
 206    * and other threads requesting this entry will potentially be blocked
 207    * until the caller repopulates the cache. If the caller choses not
 208    * to repopulate the cache, they <em>must</em> instead call
 209    * {@link #cancelUpdate(String)}.
 210    */
 211  2004396 public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
 212  2003614 return getFromCache(key, refreshPeriod, null);
 213    }
 214   
 215    /**
 216    * Retrieve an object from the cache specifying its key.
 217    *
 218    * @param key Key of the object in the cache.
 219    * @param refreshPeriod How long before the object needs refresh. To
 220    * allow the object to stay in the cache indefinitely, supply a value
 221    * of {@link CacheEntry#INDEFINITE_EXPIRY}.
 222    * @param cronExpiry A cron expression that specifies fixed date(s)
 223    * and/or time(s) that this cache entry should
 224    * expire on.
 225    *
 226    * @return The object from cache
 227    *
 228    * @throws NeedsRefreshException Thrown when the object either
 229    * doesn't exist, or exists but is stale. When this exception occurs,
 230    * the CacheEntry corresponding to the supplied key will be locked
 231    * and other threads requesting this entry will potentially be blocked
 232    * until the caller repopulates the cache. If the caller choses not
 233    * to repopulate the cache, they <em>must</em> instead call
 234    * {@link #cancelUpdate(String)}.
 235    */
 236  2012396 public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
 237  2012396 CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
 238   
 239  2012384 Object content = cacheEntry.getContent();
 240  2012384 CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
 241   
 242  2012384 boolean reload = false;
 243   
 244    // Check if this entry has expired or has not yet been added to the cache. If
 245    // so, we need to decide whether to block, serve stale content or throw a
 246    // NeedsRefreshException
 247  2012384 if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
 248   
 249    //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
 250  2012158 EntryUpdateState updateState = getUpdateState(key);
 251  2012158 try {
 252  2012158 synchronized (updateState) {
 253  2012158 if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
 254    // No one else is currently updating this entry - grab ownership
 255  1980627 updateState.startUpdate();
 256   
 257  1980627 if (cacheEntry.isNew()) {
 258  8026 accessEventType = CacheMapAccessEventType.MISS;
 259    } else {
 260  1972601 accessEventType = CacheMapAccessEventType.STALE_HIT;
 261    }
 262  31531 } else if (updateState.isUpdating()) {
 263    // Another thread is already updating the cache. We block if this
 264    // is a new entry, or blocking mode is enabled. Either putInCache()
 265    // or cancelUpdate() can cause this thread to resume.
 266  31531 if (cacheEntry.isNew() || blocking) {
 267  31527 do {
 268  122637 try {
 269  122637 updateState.wait();
 270    } catch (InterruptedException e) {
 271    }
 272  122637 } while (updateState.isUpdating());
 273   
 274  31527 if (updateState.isCancelled()) {
 275    // The updating thread cancelled the update, let this one have a go.
 276    // This increments the usage count for this EntryUpdateState instance
 277  31515 updateState.startUpdate();
 278   
 279  31515 if (cacheEntry.isNew()) {
 280  4 accessEventType = CacheMapAccessEventType.MISS;
 281    } else {
 282  31511 accessEventType = CacheMapAccessEventType.STALE_HIT;
 283    }
 284  12 } else if (updateState.isComplete()) {
 285  12 reload = true;
 286    } else {
 287  0 log.error("Invalid update state for cache entry " + key);
 288    }
 289    }
 290    } else {
 291  0 reload = true;
 292    }
 293    }
 294    } finally {
 295    //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
 296    //increased by one in startUpdate()
 297  2012158 releaseUpdateState(updateState, key);
 298    }
 299    }
 300   
 301    // If reload is true then another thread must have successfully rebuilt the cache entry
 302  2012384 if (reload) {
 303  12 cacheEntry = (CacheEntry) cacheMap.get(key);
 304   
 305  12 if (cacheEntry != null) {
 306  12 content = cacheEntry.getContent();
 307    } else {
 308  0 log.error("Could not reload cache entry after waiting for it to be rebuilt");
 309    }
 310    }
 311   
 312  2012384 dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
 313   
 314    // If we didn't end up getting a hit then we need to throw a NRE
 315  2012384 if (accessEventType != CacheMapAccessEventType.HIT) {
 316  2012142 throw new NeedsRefreshException(content);
 317    }
 318   
 319  242 return content;
 320    }
 321   
 322    /**
 323    * Set the listener to use for data persistence. Only one
 324    * <code>PersistenceListener</code> can be configured per cache.
 325    *
 326    * @param listener The implementation of a persistance listener
 327    */
 328  54 public void setPersistenceListener(PersistenceListener listener) {
 329  54 cacheMap.setPersistenceListener(listener);
 330    }
 331   
 332    /**
 333    * Retrieves the currently configured <code>PersistenceListener</code>.
 334    *
 335    * @return the cache's <code>PersistenceListener</code>, or <code>null</code>
 336    * if no listener is configured.
 337    */
 338  0 public PersistenceListener getPersistenceListener() {
 339  0 return cacheMap.getPersistenceListener();
 340    }
 341   
 342    /**
 343    * Register a listener for Cache events. The listener must implement
 344    * one of the child interfaces of the {@link CacheEventListener} interface.
 345    *
 346    * @param listener The object that listens to events.
 347    */
 348  96 public void addCacheEventListener(CacheEventListener listener, Class clazz) {
 349  96 if (CacheEventListener.class.isAssignableFrom(clazz)) {
 350  96 listenerList.add(clazz, listener);
 351    } else {
 352  0 log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener.");
 353    }
 354    }
 355   
 356    /**
 357    * Returns the list of all CacheEventListeners.
 358    * @return the CacheEventListener's list of the Cache
 359    */
 360  0 public EventListenerList getCacheEventListenerList() {
 361  0 return listenerList;
 362    }
 363   
 364    /**
 365    * Cancels any pending update for this cache entry. This should <em>only</em>
 366    * be called by the thread that is responsible for performing the update ie
 367    * the thread that received the original {@link NeedsRefreshException}.<p/>
 368    * If a cache entry is not updated (via {@link #putInCache} and this method is
 369    * not called to let OSCache know the update will not be forthcoming, subsequent
 370    * requests for this cache entry will either block indefinitely (if this is a new
 371    * cache entry or cache.blocking=true), or forever get served stale content. Note
 372    * however that there is no harm in cancelling an update on a key that either
 373    * does not exist or is not currently being updated.
 374    *
 375    * @param key The key for the cache entry in question.
 376    */
 377  2008122 public void cancelUpdate(String key) {
 378  2008122 EntryUpdateState state;
 379   
 380  2008122 if (key != null) {
 381  2008122 synchronized (updateStates) {
 382  2008122 state = (EntryUpdateState) updateStates.get(key);
 383   
 384  2008122 if (state != null) {
 385  2008122 synchronized (state) {
 386  2008122 int usageCounter = state.cancelUpdate();
 387  2008122 state.notify();
 388   
 389  2008122 checkEntryStateUpdateUsage(key, state, usageCounter);
 390    }
 391    } else {
 392  0 if (log.isErrorEnabled()) {
 393  0 log.error("internal error: expected to get a state from key [" + key + "]");
 394    }
 395    }
 396    }
 397    }
 398    }
 399   
 400    /**
 401    * 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.
 402    *
 403    * Warning: This method should always be called while holding both the updateStates field and the state parameter
 404    * @throws Exception
 405    */
 406  4024300 private void checkEntryStateUpdateUsage(String key, EntryUpdateState state, int usageCounter) {
 407    //Clean up the updateStates map to avoid a memory leak once no thread is using this EntryUpdateState instance anymore.
 408  4024300 if (usageCounter ==0) {
 409  31177 EntryUpdateState removedState = (EntryUpdateState) updateStates.remove(key);
 410  31177 if (state != removedState) {
 411  0 if (log.isErrorEnabled()) {
 412  0 log.error("internal error: removed state [" + removedState + "] from key [" + key + "] whereas we expected [" + state + "]");
 413  0 try {
 414  0 throw new Exception("states not equal");
 415    } catch (Exception e) {
 416    // TODO Auto-generated catch block
 417  0 e.printStackTrace();
 418    }
 419    }
 420    }
 421    }
 422    }
 423   
 424    /**
 425    * Flush all entries in the cache on the given date/time.
 426    *
 427    * @param date The date at which all cache entries will be flushed.
 428    */
 429  0 public void flushAll(Date date) {
 430  0 flushAll(date, null);
 431    }
 432   
 433    /**
 434    * Flush all entries in the cache on the given date/time.
 435    *
 436    * @param date The date at which all cache entries will be flushed.
 437    * @param origin The origin of this flush request (optional)
 438    */
 439  0 public void flushAll(Date date, String origin) {
 440  0 flushDateTime = date;
 441   
 442  0 if (listenerList.getListenerCount() > 0) {
 443  0 dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin);
 444    }
 445    }
 446   
 447    /**
 448    * Flush the cache entry (if any) that corresponds to the cache key supplied.
 449    * This call will flush the entry from the cache and remove the references to
 450    * it from any cache groups that it is a member of. On completion of the flush,
 451    * a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
 452    *
 453    * @param key The key of the entry to flush
 454    */
 455  0 public void flushEntry(String key) {
 456  0 flushEntry(key, null);
 457    }
 458   
 459    /**
 460    * Flush the cache entry (if any) that corresponds to the cache key supplied.
 461    * This call will mark the cache entry as flushed so that the next access
 462    * to it will cause a {@link NeedsRefreshException}. On completion of the
 463    * flush, a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
 464    *
 465    * @param key The key of the entry to flush
 466    * @param origin The origin of this flush request (optional)
 467    */
 468  0 public void flushEntry(String key, String origin) {
 469  0 flushEntry(getCacheEntry(key, null, origin), origin);
 470    }
 471   
 472    /**
 473    * Flushes all objects that belong to the supplied group. On completion
 474    * this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt> event.
 475    *
 476    * @param group The group to flush
 477    */
 478  36 public void flushGroup(String group) {
 479  36 flushGroup(group, null);
 480    }
 481   
 482    /**
 483    * Flushes all unexpired objects that belong to the supplied group. On
 484    * completion this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt>
 485    * event.
 486    *
 487    * @param group The group to flush
 488    * @param origin The origin of this flush event (optional)
 489    */
 490  36 public void flushGroup(String group, String origin) {
 491    // Flush all objects in the group
 492  36 Set groupEntries = cacheMap.getGroup(group);
 493   
 494  36 if (groupEntries != null) {
 495  35 Iterator itr = groupEntries.iterator();
 496  35 String key;
 497  35 CacheEntry entry;
 498   
 499  35 while (itr.hasNext()) {
 500  107 key = (String) itr.next();
 501  107 entry = (CacheEntry) cacheMap.get(key);
 502   
 503  107 if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
 504  50 flushEntry(entry, NESTED_EVENT);
 505    }
 506    }
 507    }
 508   
 509  36 if (listenerList.getListenerCount() > 0) {
 510  36 dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
 511    }
 512    }
 513   
 514    /**
 515    * Flush all entries with keys that match a given pattern
 516    *
 517    * @param pattern The key must contain this given value
 518    * @deprecated For performance and flexibility reasons it is preferable to
 519    * store cache entries in groups and use the {@link #flushGroup(String)} method
 520    * instead of relying on pattern flushing.
 521    */
 522  40 public void flushPattern(String pattern) {
 523  40 flushPattern(pattern, null);
 524    }
 525   
 526    /**
 527    * Flush all entries with keys that match a given pattern
 528    *
 529    * @param pattern The key must contain this given value
 530    * @param origin The origin of this flush request
 531    * @deprecated For performance and flexibility reasons it is preferable to
 532    * store cache entries in groups and use the {@link #flushGroup(String, String)}
 533    * method instead of relying on pattern flushing.
 534    */
 535  40 public void flushPattern(String pattern, String origin) {
 536    // Check the pattern
 537  40 if ((pattern != null) && (pattern.length() > 0)) {
 538  24 String key = null;
 539  24 CacheEntry entry = null;
 540  24 Iterator itr = cacheMap.keySet().iterator();
 541   
 542  24 while (itr.hasNext()) {
 543  72 key = (String) itr.next();
 544   
 545  72 if (key.indexOf(pattern) >= 0) {
 546  8 entry = (CacheEntry) cacheMap.get(key);
 547   
 548  8 if (entry != null) {
 549  8 flushEntry(entry, origin);
 550    }
 551    }
 552    }
 553   
 554  24 if (listenerList.getListenerCount() > 0) {
 555  16 dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
 556    }
 557    } else {
 558    // Empty pattern, nothing to do
 559    }
 560    }
 561   
 562    /**
 563    * Put an object in the cache specifying the key to use.
 564    *
 565    * @param key Key of the object in the cache.
 566    * @param content The object to cache.
 567    */
 568  8 public void putInCache(String key, Object content) {
 569  8 putInCache(key, content, null, null, null);
 570    }
 571   
 572    /**
 573    * Put an object in the cache specifying the key and refresh policy to use.
 574    *
 575    * @param key Key of the object in the cache.
 576    * @param content The object to cache.
 577    * @param policy Object that implements refresh policy logic
 578    */
 579  12196 public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
 580  12196 putInCache(key, content, null, policy, null);
 581    }
 582   
 583    /**
 584    * Put in object into the cache, specifying both the key to use and the
 585    * cache groups the object belongs to.
 586    *
 587    * @param key Key of the object in the cache
 588    * @param content The object to cache
 589    * @param groups The cache groups to add the object to
 590    */
 591  84 public void putInCache(String key, Object content, String[] groups) {
 592  84 putInCache(key, content, groups, null, null);
 593    }
 594   
 595    /**
 596    * Put an object into the cache specifying both the key to use and the
 597    * cache groups the object belongs to.
 598    *
 599    * @param key Key of the object in the cache
 600    * @param groups The cache groups to add the object to
 601    * @param content The object to cache
 602    * @param policy Object that implements the refresh policy logic
 603    */
 604  12288 public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin) {
 605  12288 CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin);
 606  12284 boolean isNewEntry = cacheEntry.isNew();
 607   
 608    // [CACHE-118] If we have an existing entry, create a new CacheEntry so we can still access the old one later
 609  12284 if (!isNewEntry) {
 610  4054 cacheEntry = new CacheEntry(key, policy);
 611    }
 612   
 613  12284 cacheEntry.setContent(content);
 614  12284 cacheEntry.setGroups(groups);
 615  12284 cacheMap.put(key, cacheEntry);
 616   
 617    // Signal to any threads waiting on this update that it's now ready for them
 618    // in the cache!
 619  12284 completeUpdate(key);
 620   
 621  12284 if (listenerList.getListenerCount() > 0) {
 622  120 CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
 623   
 624  120 if (isNewEntry) {
 625  80 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
 626    } else {
 627  40 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
 628    }
 629    }
 630    }
 631   
 632    /**
 633    * Unregister a listener for Cache events.
 634    *
 635    * @param listener The object that currently listens to events.
 636    */
 637  96 public void removeCacheEventListener(CacheEventListener listener, Class clazz) {
 638  96 listenerList.remove(clazz, listener);
 639    }
 640   
 641    /**
 642    * Get an entry from this cache or create one if it doesn't exist.
 643    *
 644    * @param key The key of the cache entry
 645    * @param policy Object that implements refresh policy logic
 646    * @param origin The origin of request (optional)
 647    * @return CacheEntry for the specified key.
 648    */
 649  2024684 protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) {
 650  2024684 CacheEntry cacheEntry = null;
 651   
 652    // Verify that the key is valid
 653  2024684 if ((key == null) || (key.length() == 0)) {
 654  16 throw new IllegalArgumentException("getCacheEntry called with an empty or null key");
 655    }
 656   
 657  2024668 cacheEntry = (CacheEntry) cacheMap.get(key);
 658   
 659    // if the cache entry does not exist, create a new one
 660  2024668 if (cacheEntry == null) {
 661  16268 if (log.isDebugEnabled()) {
 662  0 log.debug("No cache entry exists for key='" + key + "', creating");
 663    }
 664   
 665  16268 cacheEntry = new CacheEntry(key, policy);
 666    }
 667   
 668  2024668 return cacheEntry;
 669    }
 670   
 671    /**
 672    * Indicates whether or not the cache entry is stale.
 673    *
 674    * @param cacheEntry The cache entry to test the freshness of.
 675    * @param refreshPeriod The maximum allowable age of the entry, in seconds.
 676    * @param cronExpiry A cron expression specifying absolute date(s) and/or time(s)
 677    * that the cache entry should expire at. If the cache entry was refreshed prior to
 678    * the most recent match for the cron expression, the entry will be considered stale.
 679    *
 680    * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
 681    */
 682  2012384 protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
 683  2012384 boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry);
 684   
 685  2012068 if ((!result) && (cronExpiry != null) && (cronExpiry.length() > 0)) {
 686  0 try {
 687  0 FastCronParser parser = new FastCronParser(cronExpiry);
 688  0 result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate());
 689    } catch (ParseException e) {
 690  0 log.warn(e);
 691    }
 692    }
 693   
 694  2011497 return result;
 695    }
 696   
 697    /**
 698    * Get the updating cache entry from the update map. If one is not found,
 699    * create a new one (with state {@link EntryUpdateState#NOT_YET_UPDATING})
 700    * and add it to the map.
 701    *
 702    * @param key The cache key for this entry
 703    *
 704    * @return the CacheEntry that was found (or added to) the updatingEntries
 705    * map.
 706    */
 707  2012158 protected EntryUpdateState getUpdateState(String key) {
 708  2012158 EntryUpdateState updateState;
 709   
 710  2012158 synchronized (updateStates) {
 711    // Try to find the matching state object in the updating entry map.
 712  2012158 updateState = (EntryUpdateState) updateStates.get(key);
 713   
 714  2012158 if (updateState == null) {
 715    // It's not there so add it.
 716  31177 updateState = new EntryUpdateState();
 717  31177 updateStates.put(key, updateState);
 718    } else {
 719    //Otherwise indicate that we start using it to prevent its removal until all threads are done with it.
 720  1980981 updateState.incrementUsageCounter();
 721    }
 722    }
 723   
 724  2012158 return updateState;
 725    }
 726   
 727    /**
 728    * releases the usage that was made of the specified EntryUpdateState. When this reaches zero, the entry is removed from the map.
 729    * @param state the state to release the usage of
 730    * @param key the associated key.
 731    */
 732  2012158 protected void releaseUpdateState(EntryUpdateState state, String key) {
 733  2012158 synchronized (updateStates) {
 734  2012158 int usageCounter = state.decrementUsageCounter();
 735  2012158 checkEntryStateUpdateUsage(key, state, usageCounter);
 736    }
 737    }
 738   
 739    /**
 740    * Completely clears the cache.
 741    */
 742  28 protected void clear() {
 743  28 cacheMap.clear();
 744    }
 745   
 746    /**
 747    * Removes the update state for the specified key and notifies any other
 748    * threads that are waiting on this object. This is called automatically
 749    * by the {@link #putInCache} method, so it is possible that no EntryUpdateState was hold
 750    * when this method is called.
 751    *
 752    * @param key The cache key that is no longer being updated.
 753    */
 754  12284 protected void completeUpdate(String key) {
 755  12284 EntryUpdateState state;
 756   
 757  12284 synchronized (updateStates) {
 758  12284 state = (EntryUpdateState) updateStates.get(key);
 759   
 760  12284 if (state != null) {
 761  4020 synchronized (state) {
 762  4020 int usageCounter = state.completeUpdate();
 763  4020 state.notifyAll();
 764   
 765  4020 checkEntryStateUpdateUsage(key, state, usageCounter);
 766   
 767    }
 768    } else {
 769    //If putInCache() was called directly (i.e. not as a result of a NeedRefreshException) then no EntryUpdateState would be found.
 770    }
 771    }
 772    }
 773   
 774    /**
 775    * Completely removes a cache entry from the cache and its associated cache
 776    * groups.
 777    *
 778    * @param key The key of the entry to remove.
 779    */
 780  0 public void removeEntry(String key) {
 781  0 removeEntry(key, null);
 782    }
 783   
 784    /**
 785    * Completely removes a cache entry from the cache and its associated cache
 786    * groups.
 787    *
 788    * @param key The key of the entry to remove.
 789    * @param origin The origin of this remove request.
 790    */
 791  0 protected void removeEntry(String key, String origin) {
 792  0 CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
 793  0 cacheMap.remove(key);
 794   
 795  0 if (listenerList.getListenerCount() > 0) {
 796  0 CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
 797  0 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED, event);
 798    }
 799    }
 800   
 801    /**
 802    * Dispatch a cache entry event to all registered listeners.
 803    *
 804    * @param eventType The type of event (used to branch on the proper method)
 805    * @param event The event that was fired
 806    */
 807  174 private void dispatchCacheEntryEvent(CacheEntryEventType eventType, CacheEntryEvent event) {
 808    // Guaranteed to return a non-null array
 809  174 Object[] listeners = listenerList.getListenerList();
 810   
 811    // Process the listeners last to first, notifying
 812    // those that are interested in this event
 813  174 for (int i = listeners.length - 2; i >= 0; i -= 2) {
 814  348 if (listeners[i] == CacheEntryEventListener.class) {
 815  174 if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
 816  80 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryAdded(event);
 817  94 } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) {
 818  40 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryUpdated(event);
 819  54 } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) {
 820  54 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryFlushed(event);
 821  0 } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) {
 822  0 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryRemoved(event);
 823    }
 824    }
 825    }
 826    }
 827   
 828    /**
 829    * Dispatch a cache group event to all registered listeners.
 830    *
 831    * @param eventType The type of event (this is used to branch to the correct method handler)
 832    * @param group The cache group that the event applies to
 833    * @param origin The origin of this event (optional)
 834    */
 835  36 private void dispatchCacheGroupEvent(CacheEntryEventType eventType, String group, String origin) {
 836  36 CacheGroupEvent event = new CacheGroupEvent(this, group, origin);
 837   
 838    // Guaranteed to return a non-null array
 839  36 Object[] listeners = listenerList.getListenerList();
 840   
 841    // Process the listeners last to first, notifying
 842    // those that are interested in this event
 843  36 for (int i = listeners.length - 2; i >= 0; i -= 2) {
 844  72 if (listeners[i] == CacheEntryEventListener.class) {
 845  36 if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
 846  36 ((CacheEntryEventListener) listeners[i + 1]).cacheGroupFlushed(event);
 847    }
 848    }
 849    }
 850    }
 851   
 852    /**
 853    * Dispatch a cache map access event to all registered listeners.
 854    *
 855    * @param eventType The type of event
 856    * @param entry The entry that was affected.
 857    * @param origin The origin of this event (optional)
 858    */
 859  2012384 private void dispatchCacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) {
 860  2012384 CacheMapAccessEvent event = new CacheMapAccessEvent(eventType, entry, origin);
 861   
 862    // Guaranteed to return a non-null array
 863  2012384 Object[] listeners = listenerList.getListenerList();
 864   
 865    // Process the listeners last to first, notifying
 866    // those that are interested in this event
 867  2012384 for (int i = listeners.length - 2; i >= 0; i -= 2) {
 868  368 if (listeners[i] == CacheMapAccessEventListener.class) {
 869  184 ((CacheMapAccessEventListener) listeners[i + 1]).accessed(event);
 870    }
 871    }
 872    }
 873   
 874    /**
 875    * Dispatch a cache pattern event to all registered listeners.
 876    *
 877    * @param eventType The type of event (this is used to branch to the correct method handler)
 878    * @param pattern The cache pattern that the event applies to
 879    * @param origin The origin of this event (optional)
 880    */
 881  16 private void dispatchCachePatternEvent(CacheEntryEventType eventType, String pattern, String origin) {
 882  16 CachePatternEvent event = new CachePatternEvent(this, pattern, origin);
 883   
 884    // Guaranteed to return a non-null array
 885  16 Object[] listeners = listenerList.getListenerList();
 886   
 887    // Process the listeners last to first, notifying
 888    // those that are interested in this event
 889  16 for (int i = listeners.length - 2; i >= 0; i -= 2) {
 890  32 if (listeners[i] == CacheEntryEventListener.class) {
 891  16 if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) {
 892  16 ((CacheEntryEventListener) listeners[i + 1]).cachePatternFlushed(event);
 893    }
 894    }
 895    }
 896    }
 897   
 898    /**
 899    * Dispatches a cache-wide event to all registered listeners.
 900    *
 901    * @param eventType The type of event (this is used to branch to the correct method handler)
 902    * @param origin The origin of this event (optional)
 903    */
 904  0 private void dispatchCachewideEvent(CachewideEventType eventType, Date date, String origin) {
 905  0 CachewideEvent event = new CachewideEvent(this, date, origin);
 906   
 907    // Guaranteed to return a non-null array
 908  0 Object[] listeners = listenerList.getListenerList();
 909   
 910    // Process the listeners last to first, notifying
 911    // those that are interested in this event
 912  0 for (int i = listeners.length - 2; i >= 0; i -= 2) {
 913  0 if (listeners[i] == CacheEntryEventListener.class) {
 914  0 if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
 915  0 ((CacheEntryEventListener) listeners[i + 1]).cacheFlushed(event);
 916    }
 917    }
 918    }
 919    }
 920   
 921    /**
 922    * Flush a cache entry. On completion of the flush, a
 923    * <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
 924    *
 925    * @param entry The entry to flush
 926    * @param origin The origin of this flush event (optional)
 927    */
 928  58 private void flushEntry(CacheEntry entry, String origin) {
 929  58 String key = entry.getKey();
 930   
 931    // Flush the object itself
 932  58 entry.flush();
 933   
 934  58 if (!entry.isNew()) {
 935    // Update the entry's state in the map
 936  58 cacheMap.put(key, entry);
 937    }
 938   
 939    // Trigger an ENTRY_FLUSHED event. [CACHE-107] Do this for all flushes.
 940  58 if (listenerList.getListenerCount() > 0) {
 941  54 CacheEntryEvent event = new CacheEntryEvent(this, entry, origin);
 942  54 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED, event);
 943    }
 944    }
 945   
 946    /**
 947    * Test support only: return the number of EntryUpdateState instances within the updateStates map.
 948    */
 949  32 protected int getNbUpdateState() {
 950  32 synchronized(updateStates) {
 951  32 return updateStates.size();
 952    }
 953    }
 954   
 955   
 956    /**
 957    * Test support only: return the number of entries currently in the cache map
 958    */
 959  8 public int getNbEntries() {
 960  8 synchronized(cacheMap) {
 961  8 return cacheMap.size();
 962    }
 963    }
 964    }