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