KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opensymphony > oscache > base > Cache


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 JavaDoc;
18
19 import java.text.ParseException JavaDoc;
20
21 import java.util.*;
22
23 import javax.swing.event.EventListenerList JavaDoc;
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.5 $
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 JavaDoc {
38     /**
39      * An event that origininated from within another event.
40      */

41     public static final String JavaDoc 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 JavaDoc listenerList = new EventListenerList JavaDoc();
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     public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) {
93         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     public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String JavaDoc algorithmClass, int capacity) {
120         // Instantiate the algo class if valid
121
if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
122             try {
123                 cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
124                 cacheMap.setMaxEntries(capacity);
125             } catch (Exception JavaDoc e) {
126                 log.error("Invalid class name for cache algorithm class. " + e.toString());
127             }
128         }
129
130         if (cacheMap == null) {
131             // If we have a capacity, use LRU cache otherwise use unlimited Cache
132
if (capacity > 0) {
133                 cacheMap = new LRUCache(capacity);
134             } else {
135                 cacheMap = new UnlimitedCache();
136             }
137         }
138
139         cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
140         cacheMap.setOverflowPersistence(overflowPersistence);
141         cacheMap.setMemoryCaching(useMemoryCaching);
142
143         this.blocking = blocking;
144     }
145     
146     /**
147      * @return the maximum number of items to cache can hold.
148      */

149     public int getCapacity() {
150         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     public void setCapacity(int capacity) {
161         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     public boolean isFlushed(CacheEntry cacheEntry) {
172         if (flushDateTime != null) {
173             final long lastUpdate = cacheEntry.getLastUpdate();
174             final long flushTime = flushDateTime.getTime();
175
176             // CACHE-241: check flushDateTime with current time also
177
return (flushTime <= System.currentTimeMillis()) && (flushTime >= lastUpdate);
178         } else {
179             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     public Object JavaDoc getFromCache(String JavaDoc key) throws NeedsRefreshException {
199         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     public Object JavaDoc getFromCache(String JavaDoc key, int refreshPeriod) throws NeedsRefreshException {
221         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     public Object JavaDoc getFromCache(String JavaDoc key, int refreshPeriod, String JavaDoc cronExpiry) throws NeedsRefreshException {
246         CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
247
248         Object JavaDoc content = cacheEntry.getContent();
249         CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
250
251         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
if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
257
258             //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
259
EntryUpdateState updateState = getUpdateState(key);
260             try {
261                 synchronized (updateState) {
262                     if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
263                         // No one else is currently updating this entry - grab ownership
264
updateState.startUpdate();
265                         
266                         if (cacheEntry.isNew()) {
267                             accessEventType = CacheMapAccessEventType.MISS;
268                         } else {
269                             accessEventType = CacheMapAccessEventType.STALE_HIT;
270                         }
271                     } 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
if (cacheEntry.isNew() || blocking) {
276                             do {
277                                 try {
278                                     updateState.wait();
279                                 } catch (InterruptedException JavaDoc e) {
280                                 }
281                             } while (updateState.isUpdating());
282                             
283                             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
updateState.startUpdate();
287                                 
288                                 if (cacheEntry.isNew()) {
289                                     accessEventType = CacheMapAccessEventType.MISS;
290                                 } else {
291                                     accessEventType = CacheMapAccessEventType.STALE_HIT;
292                                 }
293                             } else if (updateState.isComplete()) {
294                                 reload = true;
295                             } else {
296                                 log.error("Invalid update state for cache entry " + key);
297                             }
298                         }
299                     } else {
300                         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
releaseUpdateState(updateState, key);
307             }
308         }
309
310         // If reload is true then another thread must have successfully rebuilt the cache entry
311
if (reload) {
312             cacheEntry = (CacheEntry) cacheMap.get(key);
313
314             if (cacheEntry != null) {
315                 content = cacheEntry.getContent();
316             } else {
317                 log.error("Could not reload cache entry after waiting for it to be rebuilt");
318             }
319         }
320
321         dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
322
323         // If we didn't end up getting a hit then we need to throw a NRE
324
if (accessEventType != CacheMapAccessEventType.HIT) {
325             throw new NeedsRefreshException(content);
326         }
327
328         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     public void setPersistenceListener(PersistenceListener listener) {
338         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     public PersistenceListener getPersistenceListener() {
348         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      */

357     public void addCacheEventListener(CacheEventListener listener, Class JavaDoc clazz) {
358         if (CacheEventListener.class.isAssignableFrom(clazz)) {
359             listenerList.add(clazz, listener);
360         } else {
361             log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener.");
362         }
363     }
364     
365     /**
366      * Returns the list of all CacheEventListeners.
367      * @return the CacheEventListener's list of the Cache
368      */

369     public EventListenerList JavaDoc getCacheEventListenerList() {
370         return listenerList;
371     }
372
373     /**
374      * Cancels any pending update for this cache entry. This should <em>only</em>
375      * be called by the thread that is responsible for performing the update ie
376      * the thread that received the original {@link NeedsRefreshException}.<p/>
377      * If a cache entry is not updated (via {@link #putInCache} and this method is
378      * not called to let OSCache know the update will not be forthcoming, subsequent
379      * requests for this cache entry will either block indefinitely (if this is a new
380      * cache entry or cache.blocking=true), or forever get served stale content. Note
381      * however that there is no harm in cancelling an update on a key that either
382      * does not exist or is not currently being updated.
383      *
384      * @param key The key for the cache entry in question.
385      */

386     public void cancelUpdate(String JavaDoc key) {
387         EntryUpdateState state;
388
389         if (key != null) {
390             synchronized (updateStates) {
391                 state = (EntryUpdateState) updateStates.get(key);
392
393                 if (state != null) {
394                     synchronized (state) {
395                         int usageCounter = state.cancelUpdate();
396                         state.notify();
397                         
398                         checkEntryStateUpdateUsage(key, state, usageCounter);
399                     }
400                 } else {
401                     if (log.isErrorEnabled()) {
402                         log.error("internal error: expected to get a state from key [" + key + "]");
403                     }
404                 }
405             }
406         }
407     }
408
409     /**
410      * 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.
411      *
412      * Warning: This method should always be called while holding both the updateStates field and the state parameter
413      * @throws Exception
414      */

415     private void checkEntryStateUpdateUsage(String JavaDoc key, EntryUpdateState state, int usageCounter) {
416         //Clean up the updateStates map to avoid a memory leak once no thread is using this EntryUpdateState instance anymore.
417
if (usageCounter ==0) {
418             EntryUpdateState removedState = (EntryUpdateState) updateStates.remove(key);
419             if (state != removedState) {
420                 if (log.isErrorEnabled()) {
421                     log.error("internal error: removed state [" + removedState + "] from key [" + key + "] whereas we expected [" + state + "]");
422                     try {
423                         throw new Exception JavaDoc("states not equal");
424                     } catch (Exception JavaDoc e) {
425                         // TODO Auto-generated catch block
426
e.printStackTrace();
427                     }
428                 }
429             }
430         }
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      */

438     public void flushAll(Date date) {
439         flushAll(date, null);
440     }
441
442     /**
443      * Flush all entries in the cache on the given date/time.
444      *
445      * @param date The date at which all cache entries will be flushed.
446      * @param origin The origin of this flush request (optional)
447      */

448     public void flushAll(Date date, String JavaDoc origin) {
449         flushDateTime = date;
450
451         if (listenerList.getListenerCount() > 0) {
452             dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin);
453         }
454     }
455
456     /**
457      * Flush the cache entry (if any) that corresponds to the cache key supplied.
458      * This call will flush the entry from the cache and remove the references to
459      * it from any cache groups that it is a member of. On completion of the flush,
460      * a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
461      *
462      * @param key The key of the entry to flush
463      */

464     public void flushEntry(String JavaDoc key) {
465         flushEntry(key, null);
466     }
467
468     /**
469      * Flush the cache entry (if any) that corresponds to the cache key supplied.
470      * This call will mark the cache entry as flushed so that the next access
471      * to it will cause a {@link NeedsRefreshException}. On completion of the
472      * flush, a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
473      *
474      * @param key The key of the entry to flush
475      * @param origin The origin of this flush request (optional)
476      */

477     public void flushEntry(String JavaDoc key, String JavaDoc origin) {
478         flushEntry(getCacheEntry(key, null, origin), origin);
479     }
480
481     /**
482      * Flushes all objects that belong to the supplied group. On completion
483      * this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt> event.
484      *
485      * @param group The group to flush
486      */

487     public void flushGroup(String JavaDoc group) {
488         flushGroup(group, null);
489     }
490
491     /**
492      * Flushes all unexpired objects that belong to the supplied group. On
493      * completion this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt>
494      * event.
495      *
496      * @param group The group to flush
497      * @param origin The origin of this flush event (optional)
498      */

499     public void flushGroup(String JavaDoc group, String JavaDoc origin) {
500         // Flush all objects in the group
501
Set groupEntries = cacheMap.getGroup(group);
502
503         if (groupEntries != null) {
504             Iterator itr = groupEntries.iterator();
505             String JavaDoc key;
506             CacheEntry entry;
507
508             while (itr.hasNext()) {
509                 key = (String JavaDoc) itr.next();
510                 entry = (CacheEntry) cacheMap.get(key);
511
512                 if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
513                     flushEntry(entry, NESTED_EVENT);
514                 }
515             }
516         }
517
518         if (listenerList.getListenerCount() > 0) {
519             dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
520         }
521     }
522
523     /**
524      * Flush all entries with keys that match a given pattern
525      *
526      * @param pattern The key must contain this given value
527      * @deprecated For performance and flexibility reasons it is preferable to
528      * store cache entries in groups and use the {@link #flushGroup(String)} method
529      * instead of relying on pattern flushing.
530      */

531     public void flushPattern(String JavaDoc pattern) {
532         flushPattern(pattern, null);
533     }
534
535     /**
536      * Flush all entries with keys that match a given pattern
537      *
538      * @param pattern The key must contain this given value
539      * @param origin The origin of this flush request
540      * @deprecated For performance and flexibility reasons it is preferable to
541      * store cache entries in groups and use the {@link #flushGroup(String, String)}
542      * method instead of relying on pattern flushing.
543      */

544     public void flushPattern(String JavaDoc pattern, String JavaDoc origin) {
545         // Check the pattern
546
if ((pattern != null) && (pattern.length() > 0)) {
547             String JavaDoc key = null;
548             CacheEntry entry = null;
549             Iterator itr = cacheMap.keySet().iterator();
550
551             while (itr.hasNext()) {
552                 key = (String JavaDoc) itr.next();
553
554                 if (key.indexOf(pattern) >= 0) {
555                     entry = (CacheEntry) cacheMap.get(key);
556
557                     if (entry != null) {
558                         flushEntry(entry, origin);
559                     }
560                 }
561             }
562
563             if (listenerList.getListenerCount() > 0) {
564                 dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
565             }
566         } else {
567             // Empty pattern, nothing to do
568
}
569     }
570
571     /**
572      * Put an object in the cache specifying the key to use.
573      *
574      * @param key Key of the object in the cache.
575      * @param content The object to cache.
576      */

577     public void putInCache(String JavaDoc key, Object JavaDoc content) {
578         putInCache(key, content, null, null, null);
579     }
580
581     /**
582      * Put an object in the cache specifying the key and refresh policy to use.
583      *
584      * @param key Key of the object in the cache.
585      * @param content The object to cache.
586      * @param policy Object that implements refresh policy logic
587      */

588     public void putInCache(String JavaDoc key, Object JavaDoc content, EntryRefreshPolicy policy) {
589         putInCache(key, content, null, policy, null);
590     }
591
592     /**
593      * Put in object into the cache, specifying both the key to use and the
594      * cache groups the object belongs to.
595      *
596      * @param key Key of the object in the cache
597      * @param content The object to cache
598      * @param groups The cache groups to add the object to
599      */

600     public void putInCache(String JavaDoc key, Object JavaDoc content, String JavaDoc[] groups) {
601         putInCache(key, content, groups, null, null);
602     }
603
604     /**
605      * Put an object into the cache specifying both the key to use and the
606      * cache groups the object belongs to.
607      *
608      * @param key Key of the object in the cache
609      * @param groups The cache groups to add the object to
610      * @param content The object to cache
611      * @param policy Object that implements the refresh policy logic
612      */

613     public void putInCache(String JavaDoc key, Object JavaDoc content, String JavaDoc[] groups, EntryRefreshPolicy policy, String JavaDoc origin) {
614         CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin);
615         boolean isNewEntry = cacheEntry.isNew();
616
617         // [CACHE-118] If we have an existing entry, create a new CacheEntry so we can still access the old one later
618
if (!isNewEntry) {
619             cacheEntry = new CacheEntry(key, policy);
620         }
621
622         cacheEntry.setContent(content);
623         cacheEntry.setGroups(groups);
624         cacheMap.put(key, cacheEntry);
625
626         // Signal to any threads waiting on this update that it's now ready for them
627
// in the cache!
628
completeUpdate(key);
629
630         if (listenerList.getListenerCount() > 0) {
631             CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
632
633             if (isNewEntry) {
634                 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
635             } else {
636                 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
637             }
638         }
639     }
640
641     /**
642      * Unregister a listener for Cache events.
643      *
644      * @param listener The object that currently listens to events.
645      */

646     public void removeCacheEventListener(CacheEventListener listener, Class JavaDoc clazz) {
647         listenerList.remove(clazz, listener);
648     }
649
650     /**
651      * Get an entry from this cache or create one if it doesn't exist.
652      *
653      * @param key The key of the cache entry
654      * @param policy Object that implements refresh policy logic
655      * @param origin The origin of request (optional)
656      * @return CacheEntry for the specified key.
657      */

658     protected CacheEntry getCacheEntry(String JavaDoc key, EntryRefreshPolicy policy, String JavaDoc origin) {
659         CacheEntry cacheEntry = null;
660
661         // Verify that the key is valid
662
if ((key == null) || (key.length() == 0)) {
663             throw new IllegalArgumentException JavaDoc("getCacheEntry called with an empty or null key");
664         }
665
666         cacheEntry = (CacheEntry) cacheMap.get(key);
667
668         // if the cache entry does not exist, create a new one
669
if (cacheEntry == null) {
670             if (log.isDebugEnabled()) {
671                 log.debug("No cache entry exists for key='" + key + "', creating");
672             }
673
674             cacheEntry = new CacheEntry(key, policy);
675         }
676
677         return cacheEntry;
678     }
679
680     /**
681      * Indicates whether or not the cache entry is stale.
682      *
683      * @param cacheEntry The cache entry to test the freshness of.
684      * @param refreshPeriod The maximum allowable age of the entry, in seconds.
685      * @param cronExpiry A cron expression specifying absolute date(s) and/or time(s)
686      * that the cache entry should expire at. If the cache entry was refreshed prior to
687      * the most recent match for the cron expression, the entry will be considered stale.
688      *
689      * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
690      */

691     protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String JavaDoc cronExpiry) {
692         boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry);
693
694         if ((!result) && (cronExpiry != null) && (cronExpiry.length() > 0)) {
695             try {
696                 FastCronParser parser = new FastCronParser(cronExpiry);
697                 result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate());
698             } catch (ParseException JavaDoc e) {
699                 log.warn(e);
700             }
701         }
702
703         return result;
704     }
705
706     /**
707      * Get the updating cache entry from the update map. If one is not found,
708      * create a new one (with state {@link EntryUpdateState#NOT_YET_UPDATING})
709      * and add it to the map.
710      *
711      * @param key The cache key for this entry
712      *
713      * @return the CacheEntry that was found (or added to) the updatingEntries
714      * map.
715      */

716     protected EntryUpdateState getUpdateState(String JavaDoc key) {
717         EntryUpdateState updateState;
718
719         synchronized (updateStates) {
720             // Try to find the matching state object in the updating entry map.
721
updateState = (EntryUpdateState) updateStates.get(key);
722
723             if (updateState == null) {
724                 // It's not there so add it.
725
updateState = new EntryUpdateState();
726                 updateStates.put(key, updateState);
727             } else {
728                 //Otherwise indicate that we start using it to prevent its removal until all threads are done with it.
729
updateState.incrementUsageCounter();
730             }
731         }
732
733         return updateState;
734     }
735
736     /**
737      * releases the usage that was made of the specified EntryUpdateState. When this reaches zero, the entry is removed from the map.
738      * @param state the state to release the usage of
739      * @param key the associated key.
740      */

741     protected void releaseUpdateState(EntryUpdateState state, String JavaDoc key) {
742         synchronized (updateStates) {
743             int usageCounter = state.decrementUsageCounter();
744             checkEntryStateUpdateUsage(key, state, usageCounter);
745         }
746     }
747     
748     /**
749      * Completely clears the cache.
750      */

751     protected void clear() {
752         cacheMap.clear();
753     }
754
755     /**
756      * Removes the update state for the specified key and notifies any other
757      * threads that are waiting on this object. This is called automatically
758      * by the {@link #putInCache} method, so it is possible that no EntryUpdateState was hold
759      * when this method is called.
760      *
761      * @param key The cache key that is no longer being updated.
762      */

763     protected void completeUpdate(String JavaDoc key) {
764         EntryUpdateState state;
765
766         synchronized (updateStates) {
767             state = (EntryUpdateState) updateStates.get(key);
768
769             if (state != null) {
770                 synchronized (state) {
771                     int usageCounter = state.completeUpdate();
772                     state.notifyAll();
773                     
774                     checkEntryStateUpdateUsage(key, state, usageCounter);
775
776                 }
777             } else {
778                 //If putInCache() was called directly (i.e. not as a result of a NeedRefreshException) then no EntryUpdateState would be found.
779
}
780         }
781     }
782
783     /**
784      * Completely removes a cache entry from the cache and its associated cache
785      * groups.
786      *
787      * @param key The key of the entry to remove.
788      */

789     public void removeEntry(String JavaDoc key) {
790         removeEntry(key, null);
791     }
792
793     /**
794      * Completely removes a cache entry from the cache and its associated cache
795      * groups.
796      *
797      * @param key The key of the entry to remove.
798      * @param origin The origin of this remove request.
799      */

800     protected void removeEntry(String JavaDoc key, String JavaDoc origin) {
801         CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
802         cacheMap.remove(key);
803
804         if (listenerList.getListenerCount() > 0) {
805             CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
806             dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED, event);
807         }
808     }
809
810     /**
811      * Dispatch a cache entry event to all registered listeners.
812      *
813      * @param eventType The type of event (used to branch on the proper method)
814      * @param event The event that was fired
815      */

816     private void dispatchCacheEntryEvent(CacheEntryEventType eventType, CacheEntryEvent event) {
817         // Guaranteed to return a non-null array
818
Object JavaDoc[] listeners = listenerList.getListenerList();
819
820         // Process the listeners last to first, notifying
821
// those that are interested in this event
822
for (int i = listeners.length - 2; i >= 0; i -= 2) {
823             if (listeners[i] == CacheEntryEventListener.class) {
824                 if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
825                     ((CacheEntryEventListener) listeners[i + 1]).cacheEntryAdded(event);
826                 } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) {
827                     ((CacheEntryEventListener) listeners[i + 1]).cacheEntryUpdated(event);
828                 } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) {
829                     ((CacheEntryEventListener) listeners[i + 1]).cacheEntryFlushed(event);
830                 } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) {
831                     ((CacheEntryEventListener) listeners[i + 1]).cacheEntryRemoved(event);
832                 }
833             }
834         }
835     }
836
837     /**
838      * Dispatch a cache group event to all registered listeners.
839      *
840      * @param eventType The type of event (this is used to branch to the correct method handler)
841      * @param group The cache group that the event applies to
842      * @param origin The origin of this event (optional)
843      */

844     private void dispatchCacheGroupEvent(CacheEntryEventType eventType, String JavaDoc group, String JavaDoc origin) {
845         CacheGroupEvent event = new CacheGroupEvent(this, group, origin);
846
847         // Guaranteed to return a non-null array
848
Object JavaDoc[] listeners = listenerList.getListenerList();
849
850         // Process the listeners last to first, notifying
851
// those that are interested in this event
852
for (int i = listeners.length - 2; i >= 0; i -= 2) {
853             if (listeners[i] == CacheEntryEventListener.class) {
854                 if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
855                     ((CacheEntryEventListener) listeners[i + 1]).cacheGroupFlushed(event);
856                 }
857             }
858         }
859     }
860
861     /**
862      * Dispatch a cache map access event to all registered listeners.
863      *
864      * @param eventType The type of event
865      * @param entry The entry that was affected.
866      * @param origin The origin of this event (optional)
867      */

868     private void dispatchCacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String JavaDoc origin) {
869         CacheMapAccessEvent event = new CacheMapAccessEvent(eventType, entry, origin);
870
871         // Guaranteed to return a non-null array
872
Object JavaDoc[] listeners = listenerList.getListenerList();
873
874         // Process the listeners last to first, notifying
875
// those that are interested in this event
876
for (int i = listeners.length - 2; i >= 0; i -= 2) {
877             if (listeners[i] == CacheMapAccessEventListener.class) {
878                 ((CacheMapAccessEventListener) listeners[i + 1]).accessed(event);
879             }
880         }
881     }
882
883     /**
884      * Dispatch a cache pattern event to all registered listeners.
885      *
886      * @param eventType The type of event (this is used to branch to the correct method handler)
887      * @param pattern The cache pattern that the event applies to
888      * @param origin The origin of this event (optional)
889      */

890     private void dispatchCachePatternEvent(CacheEntryEventType eventType, String JavaDoc pattern, String JavaDoc origin) {
891         CachePatternEvent event = new CachePatternEvent(this, pattern, origin);
892
893         // Guaranteed to return a non-null array
894
Object JavaDoc[] listeners = listenerList.getListenerList();
895
896         // Process the listeners last to first, notifying
897
// those that are interested in this event
898
for (int i = listeners.length - 2; i >= 0; i -= 2) {
899             if (listeners[i] == CacheEntryEventListener.class) {
900                 if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) {
901                     ((CacheEntryEventListener) listeners[i + 1]).cachePatternFlushed(event);
902                 }
903             }
904         }
905     }
906
907     /**
908      * Dispatches a cache-wide event to all registered listeners.
909      *
910      * @param eventType The type of event (this is used to branch to the correct method handler)
911      * @param origin The origin of this event (optional)
912      */

913     private void dispatchCachewideEvent(CachewideEventType eventType, Date date, String JavaDoc origin) {
914         CachewideEvent event = new CachewideEvent(this, date, origin);
915
916         // Guaranteed to return a non-null array
917
Object JavaDoc[] listeners = listenerList.getListenerList();
918
919         // Process the listeners last to first, notifying
920
// those that are interested in this event
921
for (int i = listeners.length - 2; i >= 0; i -= 2) {
922             if (listeners[i] == CacheEntryEventListener.class) {
923                 if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
924                     ((CacheEntryEventListener) listeners[i + 1]).cacheFlushed(event);
925                 }
926             }
927         }
928     }
929
930     /**
931      * Flush a cache entry. On completion of the flush, a
932      * <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
933      *
934      * @param entry The entry to flush
935      * @param origin The origin of this flush event (optional)
936      */

937     private void flushEntry(CacheEntry entry, String JavaDoc origin) {
938         String JavaDoc key = entry.getKey();
939
940         // Flush the object itself
941
entry.flush();
942
943         if (!entry.isNew()) {
944             // Update the entry's state in the map
945
cacheMap.put(key, entry);
946         }
947
948         // Trigger an ENTRY_FLUSHED event. [CACHE-107] Do this for all flushes.
949
if (listenerList.getListenerCount() > 0) {
950             CacheEntryEvent event = new CacheEntryEvent(this, entry, origin);
951             dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED, event);
952         }
953     }
954     
955     /**
956      * @return the total number of cache entries held in this cache.
957      */

958     public int getSize() {
959         synchronized(cacheMap) {
960             return cacheMap.size();
961         }
962     }
963
964     /**
965      * Test support only: return the number of EntryUpdateState instances within the updateStates map.
966      */

967     protected int getNbUpdateState() {
968         synchronized(updateStates) {
969             return updateStates.size();
970         }
971     }
972     
973     
974     /**
975      * Test support only: return the number of entries currently in the cache map
976      * @deprecated use getSize()
977      */

978     public int getNbEntries() {
979         synchronized(cacheMap) {
980             return cacheMap.size();
981         }
982     }
983 }
984
Popular Tags