Clover coverage report - Coverage timestamp: Sa Jul 7 2007 09:11:40 CEST FRAMES NO FRAMES |
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% |

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 |  | }