Clover coverage report - Coverage timestamp: do jan 22 2004 21:12:32 CET FRAMES NO FRAMES |
file stats: | LOC: | 861 |  | Methods: | 37
| NCLOC: | 355 |  | Classes: | 1
Source file | Conditionals | Statements | Methods | TOTAL
Cache.java | 74,5% | 80,4% | 73% | 77,7% |

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.6 $
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 set that holds keys of cache entries that are currently being built.
61 |  |
          * The cache checks against this map when a stale entry is requested.
62 |  |
          * If the requested key is in here, we know the entry is currently being
63 |  |
          * built by another thread and hence we can either block and wait or serve
64 |  |
          * the stale entry (depending on whether cache blocking is enabled or not).
65 |  |
          * <p>
66 |  |
          * We need to isolate these here since the actual CacheEntry
67 |  |
          * objects may not normally be held in memory at all (eg, if no
68 |  |
          * memory cache is configured).
69 |  |
          */
70 |  |
         private Map updateStates = new HashMap();
71 |  |
72 |  |
         /**
73 |  |
          * Indicates whether the cache blocks requests until new content has
74 |  |
          * been generated or just serves stale content instead.
75 |  |
          */
76 |  |
         private boolean blocking = false;
77 |  |
78 |  |
         /**
79 |  |
          * Create a new Cache
80 |  |
          *
81 |  |
          * @param useMemoryCaching Specify if the memory caching is going to be used
82 |  |
          * @param unlimitedDiskCache Specify if the disk caching is unlimited
83 |  |
          */
84 | 9 |
         public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache) {
85 | 9 |
             this(useMemoryCaching, unlimitedDiskCache, false, null, 0);
86 |  |
         }
87 |  |
88 |  |
         /**
89 |  |
          * Create a new Cache.
90 |  |
          *
91 |  |
          * If a valid algorithm class is specified, it will be used for this cache.
92 |  |
          * Otherwise if a capacity is specified, it will use LRUCache.
93 |  |
          * If no algorithm or capacity is specified UnlimitedCache is used.
94 |  |
          *
95 |  |
          * @see com.opensymphony.oscache.base.algorithm.LRUCache
96 |  |
          * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
97 |  |
          * @param useMemoryCaching Specify if the memory caching is going to be used
98 |  |
          * @param unlimitedDiskCache Specify if the disk caching is unlimited
99 |  |
          * @param blocking This parameter takes effect when a cache entry has
100 |  |
          * just expired and several simultaneous requests try to retrieve it. While
101 |  |
          * one request is rebuilding the content, the other requests will either
102 |  |
          * block and wait for the new content (<code>blocking == true</code>) or
103 |  |
          * instead receive a copy of the stale content so they don't have to wait
104 |  |
          * (<code>blocking == false</code>). the default is <code>false</code>,
105 |  |
          * which provides better performance but at the expense of slightly stale
106 |  |
          * data being served.
107 |  |
          * @param algorithmClass The class implementing the desired algorithm
108 |  |
          * @param capacity The capacity
109 |  |
          */
110 | 63 |
         public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean blocking, String algorithmClass, int capacity) {
111 |  |
             // Instantiate the algo class if valid
112 | 63 |
             if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
113 | 0 |
                 try {
114 | 0 |
                     cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
115 | 0 |
                     cacheMap.setMaxEntries(capacity);
116 |  |
                 } catch (Exception e) {
117 | 0 |
                     log.error("Invalid class name for cache algorithm class. " + e.toString());
118 |  |
                 }
119 |  |
             }
120 |  |
121 | 63 |
             if (cacheMap == null) {
122 |  |
                 // If we have a capacity, use LRU cache otherwise use unlimited Cache
123 | 63 |
                 if (capacity > 0) {
124 | 34 |
                     cacheMap = new LRUCache(capacity);
125 |  |
                 } else {
126 | 29 |
                     cacheMap = new UnlimitedCache();
127 |  |
                 }
128 |  |
             }
129 |  |
130 | 63 |
             cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
131 | 63 |
             cacheMap.setMemoryCaching(useMemoryCaching);
132 |  |
133 | 63 |
             this.blocking = blocking;
134 |  |
         }
135 |  |
136 |  |
         /**
137 |  |
          * Allows the capacity of the cache to be altered dynamically. Note that
138 |  |
          * some cache implementations may choose to ignore this setting (eg the
139 |  |
          * {@link UnlimitedCache} ignores this call).
140 |  |
          *
141 |  |
          * @param capacity the maximum number of items to hold in the cache.
142 |  |
          */
143 | 0 |
         public void setCapacity(int capacity) {
144 | 0 |
             cacheMap.setMaxEntries(capacity);
145 |  |
         }
146 |  |
147 |  |
         /**
148 |  |
          * Checks if the cache was flushed more recently than the CacheEntry provided.
149 |  |
          * Used to determine whether to refresh the particular CacheEntry.
150 |  |
          *
151 |  |
          * @param cacheEntry The cache entry which we're seeing whether to refresh
152 |  |
          * @return Whether or not the cache has been flushed more recently than this cache entry was updated.
153 |  |
          */
154 | 171 |
         public boolean isFlushed(CacheEntry cacheEntry) {
155 | 171 |
             if (flushDateTime != null) {
156 | 0 |
                 long lastUpdate = cacheEntry.getLastUpdate();
157 |  |
158 | 0 |
                 return (flushDateTime.getTime() >= lastUpdate);
159 |  |
             } else {
160 | 171 |
                 return false;
161 |  |
             }
162 |  |
         }
163 |  |
164 |  |
         /**
165 |  |
          * Retrieve an object from the cache specifying its key.
166 |  |
          *
167 |  |
          * @param key             Key of the object in the cache.
168 |  |
          *
169 |  |
          * @return The object from cache
170 |  |
          *
171 |  |
          * @throws NeedsRefreshException Thrown when the object either
172 |  |
          * doesn't exist, or exists but is stale. When this exception occurs,
173 |  |
          * the CacheEntry corresponding to the supplied key will be locked
174 |  |
          * and other threads requesting this entry will potentially be blocked
175 |  |
          * until the caller repopulates the cache. If the caller choses not
176 |  |
          * to repopulate the cache, they <em>must</em> instead call
177 |  |
          * {@link #cancelUpdate(String)}.
178 |  |
          */
179 | 0 |
         public Object getFromCache(String key) throws NeedsRefreshException {
180 | 0 |
             return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
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 |  |
          * @param refreshPeriod   How long before the object needs refresh. To
188 |  |
          * allow the object to stay in the cache indefinitely, supply a value
189 |  |
          * of {@link CacheEntry#INDEFINITE_EXPIRY}.
190 |  |
          *
191 |  |
          * @return The object from cache
192 |  |
          *
193 |  |
          * @throws NeedsRefreshException Thrown when the object either
194 |  |
          * doesn't exist, or exists but is stale. When this exception occurs,
195 |  |
          * the CacheEntry corresponding to the supplied key will be locked
196 |  |
          * and other threads requesting this entry will potentially be blocked
197 |  |
          * until the caller repopulates the cache. If the caller choses not
198 |  |
          * to repopulate the cache, they <em>must</em> instead call
199 |  |
          * {@link #cancelUpdate(String)}.
200 |  |
          */
201 | 291 |
         public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
202 | 291 |
             return getFromCache(key, refreshPeriod, null);
203 |  |
         }
204 |  |
205 |  |
         /**
206 |  |
          * Retrieve an object from the cache specifying its key.
207 |  |
          *
208 |  |
          * @param key             Key of the object in the cache.
209 |  |
          * @param refreshPeriod   How long before the object needs refresh. To
210 |  |
          * allow the object to stay in the cache indefinitely, supply a value
211 |  |
          * of {@link CacheEntry#INDEFINITE_EXPIRY}.
212 |  |
          * @param cronExpiry      A cron expression that specifies fixed date(s)
213 |  |
          *                        and/or time(s) that this cache entry should
214 |  |
          *                        expire on.
215 |  |
          *
216 |  |
          * @return The object from cache
217 |  |
          *
218 |  |
          * @throws NeedsRefreshException Thrown when the object either
219 |  |
          * doesn't exist, or exists but is stale. When this exception occurs,
220 |  |
          * the CacheEntry corresponding to the supplied key will be locked
221 |  |
          * and other threads requesting this entry will potentially be blocked
222 |  |
          * until the caller repopulates the cache. If the caller choses not
223 |  |
          * to repopulate the cache, they <em>must</em> instead call
224 |  |
          * {@link #cancelUpdate(String)}.
225 |  |
          */
226 | 291 |
         public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
227 | 291 |
             CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
228 |  |
229 | 282 |
             Object content = cacheEntry.getContent();
230 | 282 |
             CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
231 |  |
232 | 282 |
             boolean reload = false;
233 |  |
234 |  |
             // Check if this entry has expired or has not yet been added to the cache. If
235 |  |
             // so, we need to decide whether to block, serve stale content or throw a
236 |  |
             // NeedsRefreshException
237 | 282 |
             if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
238 | 111 |
                 EntryUpdateState updateState = getUpdateState(key);
239 |  |
240 | 111 |
                 synchronized (updateState) {
241 | 111 |
                     if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
242 |  |
                         // No one else is currently updating this entry - grab ownership
243 | 96 |
                         updateState.startUpdate();
244 |  |
245 | 96 |
                         if (cacheEntry.isNew()) {
246 | 18 |
                             accessEventType = CacheMapAccessEventType.MISS;
247 |  |
                         } else {
248 | 78 |
                             accessEventType = CacheMapAccessEventType.STALE_HIT;
249 |  |
                         }
250 | 15 |
                     } else if (updateState.isUpdating()) {
251 |  |
                         // Another thread is already updating the cache. We block if this
252 |  |
                         // is a new entry, or blocking mode is enabled. Either putInCache()
253 |  |
                         // or cancelUpdate() can cause this thread to resume.
254 | 15 |
                         if (cacheEntry.isNew() || blocking) {
255 | 12 |
                             do {
256 | 12 |
                                 try {
257 | 12 |
                                     updateState.wait();
258 |  |
                                 } catch (InterruptedException e) {
259 |  |
                                 }
260 | 12 |
                             } while (updateState.isUpdating());
261 |  |
262 | 12 |
                             if (updateState.isCancelled()) {
263 |  |
                                 // The updating thread cancelled the update, let this one have a go.
264 | 3 |
                                 updateState.startUpdate();
265 |  |
266 |  |
                                 // We put the updateState object back into the updateStates map so
267 |  |
                                 // any remaining threads waiting on this cache entry will be notified
268 |  |
                                 // once this thread has done its thing (either updated the cache or
269 |  |
                                 // cancelled the update). Without this code they'll get left hanging...
270 | 3 |
                                 synchronized (updateStates) {
271 | 3 |
                                     updateStates.put(key, updateState);
272 |  |
                                 }
273 |  |
274 | 3 |
                                 if (cacheEntry.isNew()) {
275 | 3 |
                                     accessEventType = CacheMapAccessEventType.MISS;
276 |  |
                                 } else {
277 | 0 |
                                     accessEventType = CacheMapAccessEventType.STALE_HIT;
278 |  |
                                 }
279 | 9 |
                             } else if (updateState.isComplete()) {
280 | 9 |
                                 reload = true;
281 |  |
                             } else {
282 | 0 |
                                 log.error("Invalid update state for cache entry " + key);
283 |  |
                             }
284 |  |
                         }
285 |  |
                     } else {
286 | 0 |
                         reload = true;
287 |  |
                     }
288 |  |
                 }
289 |  |
             }
290 |  |
291 |  |
             // If reload is true then another thread must have successfully rebuilt the cache entry
292 | 282 |
             if (reload) {
293 | 9 |
                 cacheEntry = (CacheEntry) cacheMap.get(key);
294 |  |
295 | 9 |
                 if (cacheEntry != null) {
296 | 9 |
                     content = cacheEntry.getContent();
297 |  |
                 } else {
298 | 0 |
                     log.error("Could not reload cache entry after waiting for it to be rebuilt");
299 |  |
                 }
300 |  |
             }
301 |  |
302 | 282 |
             dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
303 |  |
304 |  |
             // If we didn't end up getting a hit then we need to throw a NRE
305 | 282 |
             if (accessEventType != CacheMapAccessEventType.HIT) {
306 | 99 |
                 throw new NeedsRefreshException(content);
307 |  |
             }
308 |  |
309 | 183 |
             return content;
310 |  |
         }
311 |  |
312 |  |
         /**
313 |  |
          * Set the listener to use for data persistence. Only one
314 |  |
          * <code>PersistenceListener</code> can be configured per cache.
315 |  |
          *
316 |  |
          * @param listener The implementation of a persistance listener
317 |  |
          */
318 | 34 |
         public void setPersistenceListener(PersistenceListener listener) {
319 | 34 |
             cacheMap.setPersistenceListener(listener);
320 |  |
         }
321 |  |
322 |  |
         /**
323 |  |
          * Retrieves the currently configured <code>PersistenceListener</code>.
324 |  |
          *
325 |  |
          * @return the cache's <code>PersistenceListener</code>, or <code>null</code>
326 |  |
          * if no listener is configured.
327 |  |
          */
328 | 0 |
         public PersistenceListener getPersistenceListener() {
329 | 0 |
             return cacheMap.getPersistenceListener();
330 |  |
         }
331 |  |
332 |  |
         /**
333 |  |
          * Register a listener for Cache events. The listener must implement
334 |  |
          * one of the child interfaces of the {@link CacheEventListener} interface.
335 |  |
          *
336 |  |
          * @param listener  The object that listens to events.
337 |  |
          */
338 | 72 |
         public void addCacheEventListener(CacheEventListener listener, Class clazz) {
339 | 72 |
             if (CacheEventListener.class.isAssignableFrom(clazz)) {
340 | 72 |
                 listenerList.add(clazz, listener);
341 |  |
             } else {
342 | 0 |
                 log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener.");
343 |  |
             }
344 |  |
         }
345 |  |
346 |  |
         /**
347 |  |
          * Cancels any pending update for this cache entry. This should <em>only</em>
348 |  |
          * be called by the thread that is responsible for performing the update ie
349 |  |
          * the thread that received the original {@link NeedsRefreshException}.<p/>
350 |  |
          * If a cache entry is not updated (via {@link #putInCache} and this method is
351 |  |
          * not called to let OSCache know the update will not be forthcoming, subsequent
352 |  |
          * requests for this cache entry will either block indefinitely (if this is a new
353 |  |
          * cache entry or cache.blocking=true), or forever get served stale content. Note
354 |  |
          * however that there is no harm in cancelling an update on a key that either
355 |  |
          * does not exist or is not currently being updated.
356 |  |
          *
357 |  |
          * @param key The key for the cache entry in question.
358 |  |
          */
359 | 84 |
         public void cancelUpdate(String key) {
360 | 84 |
             EntryUpdateState state;
361 |  |
362 | 84 |
             if (key != null) {
363 | 84 |
                 synchronized (updateStates) {
364 | 84 |
                     state = (EntryUpdateState) updateStates.remove(key);
365 |  |
366 | 84 |
                     if (state != null) {
367 | 84 |
                         synchronized (state) {
368 | 84 |
                             state.cancelUpdate();
369 | 84 |
                             state.notify();
370 |  |
                         }
371 |  |
                     }
372 |  |
                 }
373 |  |
             }
374 |  |
         }
375 |  |
376 |  |
         /**
377 |  |
          * Flush all entries in the cache on the given date/time.
378 |  |
          *
379 |  |
          * @param date The date at which all cache entries will be flushed.
380 |  |
          */
381 | 0 |
         public void flushAll(Date date) {
382 | 0 |
             flushAll(date, null);
383 |  |
         }
384 |  |
385 |  |
         /**
386 |  |
          * Flush all entries in the cache on the given date/time.
387 |  |
          *
388 |  |
          * @param date The date at which all cache entries will be flushed.
389 |  |
          * @param origin The origin of this flush request (optional)
390 |  |
          */
391 | 0 |
         public void flushAll(Date date, String origin) {
392 | 0 |
             flushDateTime = date;
393 | 0 |
             dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin);
394 |  |
         }
395 |  |
396 |  |
         /**
397 |  |
          * Flush the cache entry (if any) that corresponds to the cache key supplied.
398 |  |
          * This call will flush the entry from the cache and remove the references to
399 |  |
          * it from any cache groups that it is a member of. On completion of the flush,
400 |  |
          * a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
401 |  |
          *
402 |  |
          * @param key The key of the entry to flush
403 |  |
          */
404 | 0 |
         public void flushEntry(String key) {
405 | 0 |
             flushEntry(key, null);
406 |  |
         }
407 |  |
408 |  |
         /**
409 |  |
          * Flush the cache entry (if any) that corresponds to the cache key supplied.
410 |  |
          * This call will mark the cache entry as flushed so that the next access
411 |  |
          * to it will cause a {@link NeedsRefreshException}. On completion of the
412 |  |
          * flush, a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
413 |  |
          *
414 |  |
          * @param key The key of the entry to flush
415 |  |
          * @param origin The origin of this flush request (optional)
416 |  |
          */
417 | 0 |
         public void flushEntry(String key, String origin) {
418 | 0 |
             flushEntry(getCacheEntry(key, null, origin), origin);
419 |  |
         }
420 |  |
421 |  |
         /**
422 |  |
          * Flushes all objects that belong to the supplied group. On completion
423 |  |
          * this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt> event.
424 |  |
          *
425 |  |
          * @param group The group to flush
426 |  |
          */
427 | 24 |
         public void flushGroup(String group) {
428 | 24 |
             flushGroup(group, null);
429 |  |
         }
430 |  |
431 |  |
         /**
432 |  |
          * Flushes all unexpired objects that belong to the supplied group. On
433 |  |
          * completion this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt>
434 |  |
          * event.
435 |  |
          *
436 |  |
          * @param group The group to flush
437 |  |
          * @param origin The origin of this flush event (optional)
438 |  |
          */
439 | 24 |
         public void flushGroup(String group, String origin) {
440 |  |
             // Flush all objects in the group
441 | 24 |
             Set groupEntries = cacheMap.getGroup(group);
442 |  |
443 | 24 |
             if (groupEntries != null) {
444 | 23 |
                 Iterator itr = groupEntries.iterator();
445 | 23 |
                 String key;
446 | 23 |
                 CacheEntry entry;
447 |  |
448 | 23 |
                 while (itr.hasNext()) {
449 | 92 |
                     key = (String) itr.next();
450 | 92 |
                     entry = (CacheEntry) cacheMap.get(key);
451 |  |
452 | 92 |
                     if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
453 | 39 |
                         flushEntry(entry, NESTED_EVENT);
454 |  |
                     }
455 |  |
                 }
456 |  |
             }
457 |  |
458 | 24 |
             dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
459 |  |
         }
460 |  |
461 |  |
         /**
462 |  |
          * Flush all entries with keys that match a given pattern
463 |  |
          *
464 |  |
          * @param  pattern The key must contain this given value
465 |  |
          * @deprecated For performance and flexibility reasons it is preferable to
466 |  |
          * store cache entries in groups and use the {@link #flushGroup(String)} method
467 |  |
          * instead of relying on pattern flushing.
468 |  |
          */
469 | 30 |
         public void flushPattern(String pattern) {
470 | 30 |
             flushPattern(pattern, null);
471 |  |
         }
472 |  |
473 |  |
         /**
474 |  |
          * Flush all entries with keys that match a given pattern
475 |  |
          *
476 |  |
          * @param  pattern The key must contain this given value
477 |  |
          * @param origin The origin of this flush request
478 |  |
          * @deprecated For performance and flexibility reasons it is preferable to
479 |  |
          * store cache entries in groups and use the {@link #flushGroup(String, String)}
480 |  |
          * method instead of relying on pattern flushing.
481 |  |
          */
482 | 30 |
         public void flushPattern(String pattern, String origin) {
483 |  |
             // Check the pattern
484 | 30 |
             if ((pattern != null) && (pattern.length() > 0)) {
485 | 18 |
                 String key = null;
486 | 18 |
                 CacheEntry entry = null;
487 | 18 |
                 Iterator itr = cacheMap.keySet().iterator();
488 |  |
489 | 18 |
                 while (itr.hasNext()) {
490 | 54 |
                     key = (String) itr.next();
491 |  |
492 | 54 |
                     if (key.indexOf(pattern) >= 0) {
493 | 6 |
                         entry = (CacheEntry) cacheMap.get(key);
494 |  |
495 | 6 |
                         if (entry != null) {
496 | 6 |
                             flushEntry(entry, origin);
497 |  |
                         }
498 |  |
                     }
499 |  |
                 }
500 |  |
501 | 18 |
                 dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
502 |  |
             } else {
503 |  |
                 // Empty pattern, nothing to do
504 |  |
             }
505 |  |
         }
506 |  |
507 |  |
         /**
508 |  |
          * Put an object in the cache specifying the key to use.
509 |  |
          *
510 |  |
          * @param key       Key of the object in the cache.
511 |  |
          * @param content   The object to cache.
512 |  |
          */
513 | 6 |
         public void putInCache(String key, Object content) {
514 | 6 |
             putInCache(key, content, null, null, null);
515 |  |
         }
516 |  |
517 |  |
         /**
518 |  |
          * Put an object in the cache specifying the key and refresh policy to use.
519 |  |
          *
520 |  |
          * @param key       Key of the object in the cache.
521 |  |
          * @param content   The object to cache.
522 |  |
          * @param policy   Object that implements refresh policy logic
523 |  |
          */
524 | 144 |
         public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
525 | 144 |
             putInCache(key, content, null, policy, null);
526 |  |
         }
527 |  |
528 |  |
         /**
529 |  |
          * Put in object into the cache, specifying both the key to use and the
530 |  |
          * cache groups the object belongs to.
531 |  |
          *
532 |  |
          * @param key       Key of the object in the cache
533 |  |
          * @param content   The object to cache
534 |  |
          * @param groups    The cache groups to add the object to
535 |  |
          */
536 | 54 |
         public void putInCache(String key, Object content, String[] groups) {
537 | 54 |
             putInCache(key, content, groups, null, null);
538 |  |
         }
539 |  |
540 |  |
         /**
541 |  |
          * Put an object into the cache specifying both the key to use and the
542 |  |
          * cache groups the object belongs to.
543 |  |
          *
544 |  |
          * @param key       Key of the object in the cache
545 |  |
          * @param groups    The cache groups to add the object to
546 |  |
          * @param content   The object to cache
547 |  |
          * @param policy    Object that implements the refresh policy logic
548 |  |
          */
549 | 204 |
         public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin) {
550 | 204 |
             CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin);
551 | 201 |
             boolean isNewEntry = cacheEntry.isNew();
552 | 201 |
             cacheEntry.setContent(content);
553 | 201 |
             cacheEntry.setGroups(groups);
554 | 201 |
             cacheMap.put(key, cacheEntry);
555 |  |
556 |  |
             // Signal to any threads waiting on this update that it's now ready for them
557 |  |
             // in the cache!
558 | 201 |
             completeUpdate(key);
559 |  |
560 | 201 |
             CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
561 |  |
562 | 201 |
             if (isNewEntry) {
563 | 164 |
                 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
564 |  |
             } else {
565 | 37 |
                 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
566 |  |
             }
567 |  |
         }
568 |  |
569 |  |
         /**
570 |  |
          * Unregister a listener for Cache events.
571 |  |
          *
572 |  |
          * @param listener  The object that currently listens to events.
573 |  |
          */
574 | 72 |
         public void removeCacheEventListener(CacheEventListener listener, Class clazz) {
575 | 72 |
             listenerList.remove(clazz, listener);
576 |  |
         }
577 |  |
578 |  |
         /**
579 |  |
          * Get an entry from this cache or create one if it doesn't exist.
580 |  |
          *
581 |  |
          * @param key    The key of the cache entry
582 |  |
          * @param policy Object that implements refresh policy logic
583 |  |
          * @param origin The origin of request (optional)
584 |  |
          * @return CacheEntry for the specified key.
585 |  |
          */
586 | 495 |
         protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) {
587 | 495 |
             CacheEntry cacheEntry = null;
588 |  |
589 |  |
             // Verify that the key is valid
590 | 495 |
             if ((key == null) || (key.length() == 0)) {
591 | 12 |
                 throw new IllegalArgumentException("getCacheEntry called with an empty or null key");
592 |  |
             }
593 |  |
594 | 483 |
             cacheEntry = (CacheEntry) cacheMap.get(key);
595 |  |
596 |  |
             // if the cache entry does not exist, create a new one
597 | 483 |
             if (cacheEntry == null) {
598 | 191 |
                 if (log.isDebugEnabled()) {
599 | 0 |
                     log.debug("No cache entry exists for key='" + key + "', creating");
600 |  |
                 }
601 |  |
602 | 191 |
                 cacheEntry = new CacheEntry(key, policy);
603 |  |
             }
604 |  |
605 | 483 |
             return cacheEntry;
606 |  |
         }
607 |  |
608 |  |
         /**
609 |  |
          * Indicates whether or not the cache entry is stale.
610 |  |
          *
611 |  |
          * @param cacheEntry     The cache entry to test the freshness of.
612 |  |
          * @param refreshPeriod  The maximum allowable age of the entry, in seconds.
613 |  |
          * @param cronExpiry     A cron expression specifying absolute date(s) and/or time(s)
614 |  |
          * that the cache entry should expire at. If the cache entry was refreshed prior to
615 |  |
          * the most recent match for the cron expression, the entry will be considered stale.
616 |  |
          *
617 |  |
          * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
618 |  |
          */
619 | 282 |
         protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
620 | 282 |
             boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry);
621 |  |
622 | 282 |
             if ((cronExpiry != null) && (cronExpiry.length() > 0)) {
623 | 0 |
                 try {
624 | 0 |
                     FastCronParser parser = new FastCronParser(cronExpiry);
625 | 0 |
                     result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate());
626 |  |
                 } catch (ParseException e) {
627 | 0 |
                     log.warn(e);
628 |  |
                 }
629 |  |
             }
630 |  |
631 | 282 |
             return result;
632 |  |
         }
633 |  |
634 |  |
         /**
635 |  |
          * Get the updating cache entry from the update map. If one is not found,
636 |  |
          * create a new one (with state {@link EntryUpdateState#NOT_YET_UPDATING})
637 |  |
          * and add it to the map.
638 |  |
          *
639 |  |
          * @param key The cache key for this entry
640 |  |
          *
641 |  |
          * @return the CacheEntry that was found (or added to) the updatingEntries
642 |  |
          * map.
643 |  |
          */
644 | 111 |
         protected EntryUpdateState getUpdateState(String key) {
645 | 111 |
             EntryUpdateState updateState;
646 |  |
647 | 111 |
             synchronized (updateStates) {
648 |  |
                 // Try to find the matching state object in the updating entry map.
649 | 111 |
                 updateState = (EntryUpdateState) updateStates.get(key);
650 |  |
651 | 111 |
                 if (updateState == null) {
652 |  |
                     // It's not there so add it.
653 | 96 |
                     updateState = new EntryUpdateState();
654 | 96 |
                     updateStates.put(key, updateState);
655 |  |
                 }
656 |  |
             }
657 |  |
658 | 111 |
             return updateState;
659 |  |
         }
660 |  |
661 |  |
         /**
662 |  |
          * Completely clears the cache.
663 |  |
          */
664 | 9 |
         protected void clear() {
665 | 9 |
             cacheMap.clear();
666 |  |
         }
667 |  |
668 |  |
         /**
669 |  |
          * Removes the update state for the specified key and notifies any other
670 |  |
          * threads that are waiting on this object. This is called automatically
671 |  |
          * by the {@link #putInCache} method.
672 |  |
          *
673 |  |
          * @param key The cache key that is no longer being updated.
674 |  |
          */
675 | 201 |
         protected void completeUpdate(String key) {
676 | 201 |
             EntryUpdateState state;
677 |  |
678 | 201 |
             synchronized (updateStates) {
679 | 201 |
                 state = (EntryUpdateState) updateStates.remove(key);
680 |  |
681 | 201 |
                 if (state != null) {
682 | 15 |
                     synchronized (state) {
683 | 15 |
                         state.completeUpdate();
684 | 15 |
                         state.notifyAll();
685 |  |
                     }
686 |  |
                 }
687 |  |
             }
688 |  |
         }
689 |  |
690 |  |
         /**
691 |  |
          * Completely removes a cache entry from the cache and its associated cache
692 |  |
          * groups.
693 |  |
          *
694 |  |
          * @param key The key of the entry to remove.
695 |  |
          */
696 | 0 |
         protected void removeEntry(String key) {
697 | 0 |
             removeEntry(key, null);
698 |  |
         }
699 |  |
700 |  |
         /**
701 |  |
          * Completely removes a cache entry from the cache and its associated cache
702 |  |
          * groups.
703 |  |
          *
704 |  |
          * @param key    The key of the entry to remove.
705 |  |
          * @param origin The origin of this remove request.
706 |  |
          */
707 | 0 |
         protected void removeEntry(String key, String origin) {
708 | 0 |
             CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
709 | 0 |
             cacheMap.remove(key);
710 |  |
711 | 0 |
             CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
712 | 0 |
             dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED, event);
713 |  |
         }
714 |  |
715 |  |
         /**
716 |  |
          * Dispatch a cache entry event to all registered listeners.
717 |  |
          *
718 |  |
          * @param eventType   The type of event (used to branch on the proper method)
719 |  |
          * @param event       The event that was fired
720 |  |
          */
721 | 246 |
         private void dispatchCacheEntryEvent(CacheEntryEventType eventType, CacheEntryEvent event) {
722 |  |
             // Guaranteed to return a non-null array
723 | 246 |
             Object[] listeners = listenerList.getListenerList();
724 |  |
725 |  |
             // Process the listeners last to first, notifying
726 |  |
             // those that are interested in this event
727 | 246 |
             for (int i = listeners.length - 2; i >= 0; i -= 2) {
728 | 246 |
                 if (listeners[i] == CacheEntryEventListener.class) {
729 | 123 |
                     if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
730 | 50 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheEntryAdded(event);
731 | 73 |
                     } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) {
732 | 31 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheEntryUpdated(event);
733 | 42 |
                     } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) {
734 | 42 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheEntryFlushed(event);
735 | 0 |
                     } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) {
736 | 0 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheEntryRemoved(event);
737 |  |
                     }
738 |  |
                 }
739 |  |
             }
740 |  |
         }
741 |  |
742 |  |
         /**
743 |  |
          * Dispatch a cache group event to all registered listeners.
744 |  |
          *
745 |  |
          * @param eventType The type of event (this is used to branch to the correct method handler)
746 |  |
          * @param group     The cache group that the event applies to
747 |  |
          * @param origin      The origin of this event (optional)
748 |  |
          */
749 | 24 |
         private void dispatchCacheGroupEvent(CacheEntryEventType eventType, String group, String origin) {
750 | 24 |
             CacheGroupEvent event = new CacheGroupEvent(this, group, origin);
751 |  |
752 |  |
             // Guaranteed to return a non-null array
753 | 24 |
             Object[] listeners = listenerList.getListenerList();
754 |  |
755 |  |
             // Process the listeners last to first, notifying
756 |  |
             // those that are interested in this event
757 | 24 |
             for (int i = listeners.length - 2; i >= 0; i -= 2) {
758 | 48 |
                 if (listeners[i] == CacheEntryEventListener.class) {
759 | 24 |
                     if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
760 | 24 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheGroupFlushed(event);
761 |  |
                     }
762 |  |
                 }
763 |  |
             }
764 |  |
         }
765 |  |
766 |  |
         /**
767 |  |
          * Dispatch a cache map access event to all registered listeners.
768 |  |
          *
769 |  |
          * @param eventType     The type of event
770 |  |
          * @param entry         The entry that was affected.
771 |  |
          * @param origin        The origin of this event (optional)
772 |  |
          */
773 | 282 |
         private void dispatchCacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) {
774 | 282 |
             CacheMapAccessEvent event = new CacheMapAccessEvent(eventType, entry, origin);
775 |  |
776 |  |
             // Guaranteed to return a non-null array
777 | 282 |
             Object[] listeners = listenerList.getListenerList();
778 |  |
779 |  |
             // Process the listeners last to first, notifying
780 |  |
             // those that are interested in this event
781 | 282 |
             for (int i = listeners.length - 2; i >= 0; i -= 2) {
782 | 270 |
                 if (listeners[i] == CacheMapAccessEventListener.class) {
783 | 135 |
                     ((CacheMapAccessEventListener) listeners[i + 1]).accessed(event);
784 |  |
                 }
785 |  |
             }
786 |  |
         }
787 |  |
788 |  |
         /**
789 |  |
          * Dispatch a cache pattern event to all registered listeners.
790 |  |
          *
791 |  |
          * @param eventType The type of event (this is used to branch to the correct method handler)
792 |  |
          * @param pattern     The cache pattern that the event applies to
793 |  |
          * @param origin      The origin of this event (optional)
794 |  |
          */
795 | 18 |
         private void dispatchCachePatternEvent(CacheEntryEventType eventType, String pattern, String origin) {
796 | 18 |
             CachePatternEvent event = new CachePatternEvent(this, pattern, origin);
797 |  |
798 |  |
             // Guaranteed to return a non-null array
799 | 18 |
             Object[] listeners = listenerList.getListenerList();
800 |  |
801 |  |
             // Process the listeners last to first, notifying
802 |  |
             // those that are interested in this event
803 | 18 |
             for (int i = listeners.length - 2; i >= 0; i -= 2) {
804 | 24 |
                 if (listeners[i] == CacheEntryEventListener.class) {
805 | 12 |
                     if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) {
806 | 12 |
                         ((CacheEntryEventListener) listeners[i + 1]).cachePatternFlushed(event);
807 |  |
                     }
808 |  |
                 }
809 |  |
             }
810 |  |
         }
811 |  |
812 |  |
         /**
813 |  |
          * Dispatches a cache-wide event to all registered listeners.
814 |  |
          *
815 |  |
          * @param eventType The type of event (this is used to branch to the correct method handler)
816 |  |
          * @param origin The origin of this event (optional)
817 |  |
          */
818 | 0 |
         private void dispatchCachewideEvent(CachewideEventType eventType, Date date, String origin) {
819 | 0 |
             CachewideEvent event = new CachewideEvent(this, date, origin);
820 |  |
821 |  |
             // Guaranteed to return a non-null array
822 | 0 |
             Object[] listeners = listenerList.getListenerList();
823 |  |
824 |  |
             // Process the listeners last to first, notifying
825 |  |
             // those that are interested in this event
826 | 0 |
             for (int i = listeners.length - 2; i >= 0; i -= 2) {
827 | 0 |
                 if (listeners[i] == CacheEntryEventListener.class) {
828 | 0 |
                     if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
829 | 0 |
                         ((CacheEntryEventListener) listeners[i + 1]).cacheFlushed(event);
830 |  |
                     }
831 |  |
                 }
832 |  |
             }
833 |  |
         }
834 |  |
835 |  |
         /**
836 |  |
          * Flush a cache entry. On completion of the flush, a
837 |  |
          * <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
838 |  |
          *
839 |  |
          * @param entry The entry to flush
840 |  |
          * @param origin The origin of this flush event (optional)
841 |  |
          */
842 | 45 |
         private void flushEntry(CacheEntry entry, String origin) {
843 | 45 |
             String key = entry.getKey();
844 |  |
845 |  |
             // Flush the object itself
846 | 45 |
             entry.flush();
847 |  |
848 | 45 |
             if (!entry.isNew()) {
849 |  |
                 // Update the entry's state in the map
850 | 45 |
                 cacheMap.put(key, entry);
851 |  |
852 |  |
                 // Trigger an ENTRY_FLUSHED event
853 | 45 |
                 CacheEntryEvent event = new CacheEntryEvent(this, entry, origin);
854 | 45 |
                 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED, event);
855 |  |
             } else {
856 |  |
                 // The entry did not exist in the cache anyway, nothing to flush
857 |  |
                 // TODO: Do we need an event for this?
858 |  |
             }
859 |  |
         }
860 |  |
     }
861 |  |