1 /**
2 * Copyright 2003-2006 Greg Luck
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache;
18
19 import net.sf.ehcache.event.RegisteredEventListeners;
20 import net.sf.ehcache.store.DiskStore;
21 import net.sf.ehcache.store.MemoryStore;
22 import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import java.io.IOException;
27 import java.io.Serializable;
28 import java.net.InetAddress;
29 import java.net.UnknownHostException;
30 import java.rmi.server.UID;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36
37 /**
38 * Cache is the central class in ehcache. Caches have {@link Element}s and are managed
39 * by the {@link CacheManager}. The Cache performs logical actions. It delegates physical
40 * implementations to its {@link net.sf.ehcache.store.Store}s.
41 * <p/>
42 * A reference to a Cache can be obtained through the {@link CacheManager}. A Cache thus obtained
43 * is guaranteed to have status {@link Status#STATUS_ALIVE}. This status is checked for any method which
44 * throws {@link IllegalStateException} and the same thrown if it is not alive. This would normally
45 * happen if a call is made after {@link CacheManager#shutdown} is invoked.
46 * <p/>
47 * Cache is threadsafe.
48 * <p/>
49 * Statistics on cache usage are collected and made available through public methods.
50 *
51 * @author Greg Luck
52 * @version $Id: Cache.java 52 2006-04-24 14:50:03Z gregluck $
53 */
54 public final class Cache implements Cloneable {
55
56 /**
57 * A reserved word for cache names. It denotes a default configuration
58 * which is applied to caches created without configuration.
59 */
60 public static final String DEFAULT_CACHE_NAME = "default";
61
62 /**
63 * System Property based method of disabling ehcache. If disabled no elements will be added to a cache.
64 * <p/>
65 * Set the property "net.sf.ehcache.disabled=true" to disable ehcache.
66 * <p/>
67 * This can easily be done using <code>java -Dnet.sf.ehcache.disabled=true</code> in the command line.
68 */
69 public static final String NET_SF_EHCACHE_DISABLED = "net.sf.ehcache.disabled";
70
71 {
72 String value = System.getProperty(NET_SF_EHCACHE_DISABLED);
73 if (value != null) {
74 disabled = value.equalsIgnoreCase("true");
75 }
76 }
77
78 /**
79 * The default interval between runs of the expiry thread.
80 */
81 public static final long DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS = 120;
82
83 private static final Log LOG = LogFactory.getLog(Cache.class.getName());
84
85 private static final int MS_PER_SECOND = 1000;
86
87 private static final MemoryStoreEvictionPolicy DEFAULT_MEMORY_STORE_EVICTION_POLICY = MemoryStoreEvictionPolicy.LRU;
88
89 private boolean disabled;
90
91
92 private String name;
93
94 private DiskStore diskStore;
95
96 private final String diskStorePath;
97
98 private Status status;
99
100 private final int maxElementsInMemory;
101
102 private MemoryStoreEvictionPolicy memoryStoreEvictionPolicy;
103
104 /**
105 * Whether cache elements in this cache overflowToDisk.
106 */
107 private final boolean overflowToDisk;
108
109 /**
110 * The interval in seconds between runs of the disk expiry thread. 2 minutes is the default.
111 * This is not the same thing as time to live or time to idle. When the thread runs it checks
112 * these things. So this value is how often we check for expiry.
113 */
114 private final long diskExpiryThreadIntervalSeconds;
115
116 /**
117 * For caches that overflow to disk, does the disk cache persist between CacheManager instances.
118 */
119 private final boolean diskPersistent;
120
121 /**
122 * The shutdown hook thread for {@link #diskPersistent} caches. This thread
123 * must be unregistered as a shutdown hook, when the cache is disposed.
124 * Otherwise the cache is not GC-able.
125 */
126 private Thread shutdownHook;
127
128 /**
129 * Whether elements are eternal, which is the same as non-expiring.
130 */
131 private final boolean eternal;
132
133 /**
134 * The maximum time between creation time and when an element expires.
135 * Is only used if the element is not eternal.
136 */
137 private final long timeToLiveSeconds;
138
139 /**
140 * The maximum amount of time between {@link #get(Object)}s before an element expires.
141 */
142 private final long timeToIdleSeconds;
143
144
145 /**
146 * Cache hit count.
147 */
148 private int hitCount;
149
150 /**
151 * Memory cache hit count.
152 */
153 private int memoryStoreHitCount;
154
155 /**
156 * Auxiliary hit counts broken down by auxiliary.
157 */
158 private int diskStoreHitCount;
159
160 /**
161 * Count of misses where element was not found.
162 */
163 private int missCountNotFound;
164
165 /**
166 * Count of misses where element was expired.
167 */
168 private int missCountExpired;
169
170 /**
171 * The {@link MemoryStore} of this {@link Cache}. All caches have a memory store.
172 */
173 private MemoryStore memoryStore;
174
175 private RegisteredEventListeners registeredEventListeners;
176
177 private String guid;
178
179 {
180 try {
181 guid = new StringBuffer()
182 .append(InetAddress.getLocalHost())
183 .append("-")
184 .append(new UID())
185 .toString();
186 } catch (UnknownHostException e) {
187 LOG.error("Could not create GUID: " + e.getMessage());
188 }
189 }
190
191 private CacheManager cacheManager;
192
193 /**
194 * 1.0 Constructor.
195 * <p/>
196 * The {@link net.sf.ehcache.config.ConfigurationFactory} and clients can create these.
197 * <p/>
198 * A client can specify their own settings here and pass the {@link Cache} object
199 * into {@link CacheManager#addCache} to specify parameters other than the defaults.
200 * <p/>
201 * Only the CacheManager can initialise them.
202 * <p/>
203 * This constructor creates disk stores, if specified, that do not persist between restarts.
204 * <p/>
205 * The default expiry thread interval of 120 seconds is used. This is the interval between runs
206 * of the expiry thread, where it checks the disk store for expired elements. It is not the
207 * the timeToLiveSeconds.
208 *
209 * @param name Cache name
210 * @param maxElementsInMemory Max elements in memory
211 * @param overflowToDisk Overflow to disk (boolean)
212 * @param eternal Whether the elements expire
213 * @param timeToLiveSeconds
214 * @param timeToIdleSeconds
215 * @since 1.0
216 */
217 public Cache(String name, int maxElementsInMemory, boolean overflowToDisk,
218 boolean eternal, long timeToLiveSeconds, long timeToIdleSeconds) {
219 this(name, maxElementsInMemory, DEFAULT_MEMORY_STORE_EVICTION_POLICY, overflowToDisk,
220 null, eternal, timeToLiveSeconds, timeToIdleSeconds, false, DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS, null);
221 }
222
223
224 /**
225 * 1.1 Constructor.
226 * <p/>
227 * The {@link net.sf.ehcache.config.ConfigurationFactory} and clients can create these.
228 * <p/>
229 * A client can specify their own settings here and pass the {@link Cache} object
230 * into {@link CacheManager#addCache} to specify parameters other than the defaults.
231 * <p/>
232 * Only the CacheManager can initialise them.
233 *
234 * @param name
235 * @param maxElementsInMemory
236 * @param overflowToDisk
237 * @param eternal
238 * @param timeToLiveSeconds
239 * @param timeToIdleSeconds
240 * @param diskPersistent
241 * @param diskExpiryThreadIntervalSeconds
242 *
243 * @since 1.1
244 */
245 public Cache(String name,
246 int maxElementsInMemory,
247 boolean overflowToDisk,
248 boolean eternal,
249 long timeToLiveSeconds,
250 long timeToIdleSeconds,
251 boolean diskPersistent,
252 long diskExpiryThreadIntervalSeconds) {
253 this(name, maxElementsInMemory, DEFAULT_MEMORY_STORE_EVICTION_POLICY, overflowToDisk, null,
254 eternal, timeToLiveSeconds, timeToIdleSeconds, diskPersistent, diskExpiryThreadIntervalSeconds, null);
255 }
256
257 /**
258 * 1.2 Constructor
259 * <p/>
260 * The {@link net.sf.ehcache.config.ConfigurationFactory} and clients can create these.
261 * <p/>
262 * A client can specify their own settings here and pass the {@link Cache} object
263 * into {@link CacheManager#addCache} to specify parameters other than the defaults.
264 * <p/>
265 * Only the CacheManager can initialise them.
266 *
267 * @param name
268 * @param maxElementsInMemory
269 * @param memoryStoreEvictionPolicy one of LRU, LFU and FIFO. Optionally null, in which case it will be set to LRU.
270 * @param overflowToDisk
271 * @param diskStorePath
272 * @param eternal
273 * @param timeToLiveSeconds
274 * @param timeToIdleSeconds
275 * @param diskPersistent
276 * @param diskExpiryThreadIntervalSeconds
277 *
278 * @param registeredEventListeners a notification service. Optionally null, in which case a new
279 * one with no registered listeners will be created.
280 * @since 1.2
281 */
282 public Cache(String name,
283 int maxElementsInMemory,
284 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
285 boolean overflowToDisk,
286 String diskStorePath,
287 boolean eternal,
288 long timeToLiveSeconds,
289 long timeToIdleSeconds,
290 boolean diskPersistent,
291 long diskExpiryThreadIntervalSeconds,
292 RegisteredEventListeners registeredEventListeners) {
293 this.name = name;
294 this.maxElementsInMemory = maxElementsInMemory;
295 this.memoryStoreEvictionPolicy = memoryStoreEvictionPolicy;
296 this.overflowToDisk = overflowToDisk;
297 this.eternal = eternal;
298 this.timeToLiveSeconds = timeToLiveSeconds;
299 this.timeToIdleSeconds = timeToIdleSeconds;
300 this.diskPersistent = diskPersistent;
301 if (diskStorePath == null) {
302 this.diskStorePath = System.getProperty("java.io.tmpdir");
303 } else {
304 this.diskStorePath = diskStorePath;
305 }
306
307 if (registeredEventListeners == null) {
308 this.registeredEventListeners = new RegisteredEventListeners(this);
309 } else {
310 this.registeredEventListeners = registeredEventListeners;
311 }
312
313
314 if (diskExpiryThreadIntervalSeconds == 0) {
315 this.diskExpiryThreadIntervalSeconds = DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS;
316 } else {
317 this.diskExpiryThreadIntervalSeconds = diskExpiryThreadIntervalSeconds;
318 }
319
320
321 if (memoryStoreEvictionPolicy == null) {
322 this.memoryStoreEvictionPolicy = DEFAULT_MEMORY_STORE_EVICTION_POLICY;
323 }
324
325 changeStatus(Status.STATUS_UNINITIALISED);
326 }
327
328
329 /**
330 * Newly created caches do not have a {@link net.sf.ehcache.store.MemoryStore} or a {@link net.sf.ehcache.store.DiskStore}.
331 * <p/>
332 * This method creates those and makes the cache ready to accept elements
333 */
334 final synchronized void initialise() {
335 if (!status.equals(Status.STATUS_UNINITIALISED)) {
336 throw new IllegalStateException("Cannot initialise the " + name
337 + " cache because its status is not STATUS_UNINITIALISED");
338 }
339
340 if (maxElementsInMemory == 0) {
341 if (LOG.isWarnEnabled()) {
342 LOG.warn("Cache: " + name + " has a maxElementsInMemory of 0. It is strongly recommended to " +
343 "have a maximumSize of at least 1. Performance is halved by not using a MemoryStore.");
344 }
345 }
346
347 if (overflowToDisk) {
348 diskStore = new DiskStore(this, diskStorePath);
349 }
350
351 memoryStore = MemoryStore.create(this, diskStore);
352
353
354 if (diskPersistent) {
355 addShutdownHook();
356 }
357
358 changeStatus(Status.STATUS_ALIVE);
359
360 if (LOG.isDebugEnabled()) {
361 LOG.debug("Initialised cache: " + name);
362 }
363
364 if (disabled) {
365 if (LOG.isWarnEnabled()) {
366 LOG.warn("Cache: " + name + " is disabled because the " + NET_SF_EHCACHE_DISABLED
367 + " property was set to true. No elements will be added to the cache.");
368 }
369 }
370
371
372 }
373
374 private void changeStatus(Status status) {
375 this.status = status;
376 }
377
378
379 /**
380 * Some caches might be persistent, so we want to add a shutdown hook if that is the
381 * case, so that the data and index can be written to disk.
382 */
383 private void addShutdownHook() {
384 Thread localShutdownHook = new Thread() {
385 public void run() {
386 synchronized (this) {
387 if (status.equals(Status.STATUS_ALIVE)) {
388
389
390 Cache.this.shutdownHook = null;
391
392 LOG.debug("VM shutting down with the disk store for " + name
393 + " still active. The disk store is persistent. Calling dispose...");
394 dispose();
395 }
396 }
397 }
398 };
399
400 Runtime.getRuntime().addShutdownHook(localShutdownHook);
401 shutdownHook = localShutdownHook;
402 }
403
404
405 /**
406 * Remove the shutdown hook to prevent leaving orphaned caches around. This
407 * is called by {@link #dispose()} AFTER the status has been set to shutdown.
408 */
409 private void removeShutdownHook() {
410 if (shutdownHook != null) {
411
412 Runtime.getRuntime().removeShutdownHook(shutdownHook);
413
414
415 shutdownHook.start();
416
417 shutdownHook = null;
418 }
419 }
420
421
422 /**
423 * Put an element in the cache.
424 * <p/>
425 * Resets the access statistics on the element, which would be the case if it has previously been
426 * gotten from a cache, and is now being put back.
427 * <p/>
428 * Also notifies the CacheEventListener that:
429 * <ul>
430 * <li>the element was put, but only if the Element was actually put.
431 * <li>if the element exists in the cache, that an update has occurred, even if the element would be expired
432 * if it was requested
433 * </ul>
434 *
435 * @param element An object. If Serializable it can fully participate in replication and the DiskStore.
436 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
437 * @throws IllegalArgumentException if the element is null
438 */
439 public final synchronized void put(Element element) throws IllegalArgumentException, IllegalStateException,
440 CacheException {
441 put(element, false);
442 }
443
444
445 /**
446 * Put an element in the cache.
447 * <p/>
448 * Resets the access statistics on the element, which would be the case if it has previously been
449 * gotten from a cache, and is now being put back.
450 * <p/>
451 * Also notifies the CacheEventListener that:
452 * <ul>
453 * <li>the element was put, but only if the Element was actually put.
454 * <li>if the element exists in the cache, that an update has occurred, even if the element would be expired
455 * if it was requested
456 * </ul>
457 *
458 * @param element An object. If Serializable it can fully participate in replication and the DiskStore.
459 * @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
460 * further notification to doNotNotifyCacheReplicators cache peers
461 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
462 * @throws IllegalArgumentException if the element is null
463 */
464 public final synchronized void put(Element element, boolean doNotNotifyCacheReplicators) throws IllegalArgumentException,
465 IllegalStateException,
466 CacheException {
467 checkStatus();
468
469 if (disabled) {
470 return;
471 }
472
473 if (element == null) {
474 throw new IllegalArgumentException("Element cannot be null");
475 }
476 element.resetAccessStatistics();
477
478 boolean elementExists = false;
479 if (registeredEventListeners != null) {
480 Object key = element.getObjectKey();
481 elementExists = isElementInMemory(key) || isElementOnDisk(key);
482 }
483
484 memoryStore.put(element);
485
486 if (elementExists) {
487 registeredEventListeners.notifyElementUpdated(element, doNotNotifyCacheReplicators);
488 } else {
489 registeredEventListeners.notifyElementPut(element, doNotNotifyCacheReplicators);
490 }
491
492 }
493
494
495 /**
496 * Put an element in the cache, without updating statistics, or updating listeners. This is meant to be used
497 * in conjunction with {@link #getQuiet}
498 *
499 * @param element An object. If Serializable it can fully participate in replication and the DiskStore.
500 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
501 * @throws IllegalArgumentException if the element is null
502 */
503 public final synchronized void putQuiet(Element element) throws IllegalArgumentException, IllegalStateException,
504 CacheException {
505 checkStatus();
506
507 if (disabled) {
508 return;
509 }
510
511 if (element == null) {
512 throw new IllegalArgumentException("Element cannot be null");
513 }
514 memoryStore.put(element);
515 }
516
517 /**
518 * Gets an element from the cache. Updates Element Statistics
519 * <p/>
520 * Note that the Element's lastAccessTime is always the time of this get.
521 * Use {@link #getQuiet(Object)} to peak into the Element to see its last access time with get
522 *
523 * @param key a serializable value
524 * @return the element, or null, if it does not exist.
525 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
526 * @see #isExpired
527 */
528 public final synchronized Element get(Serializable key) throws IllegalStateException, CacheException {
529 return get((Object) key);
530 }
531
532
533 /**
534 * Gets an element from the cache. Updates Element Statistics
535 * <p/>
536 * Note that the Element's lastAccessTime is always the time of this get.
537 * Use {@link #getQuiet(Object)} to peak into the Element to see its last access time with get
538 *
539 * @param key an Object value
540 * @return the element, or null, if it does not exist.
541 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
542 * @see #isExpired
543 * @since 1.2
544 */
545 public final synchronized Element get(Object key) throws IllegalStateException, CacheException {
546 checkStatus();
547 Element element;
548
549 element = searchInMemoryStore(key, true);
550 if (element == null && overflowToDisk) {
551 element = searchInDiskStore(key, true);
552 }
553
554 if (element == null) {
555 missCountNotFound++;
556 if (LOG.isTraceEnabled()) {
557 LOG.trace(name + " cache - Miss");
558 }
559 return null;
560 } else {
561 hitCount++;
562 return element;
563 }
564 }
565
566 /**
567 * Gets an element from the cache, without updating Element statistics. Cache statistics are
568 * still updated.
569 * <p/>
570 *
571 * @param key a serializable value
572 * @return the element, or null, if it does not exist.
573 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
574 * @see #isExpired
575 */
576 public final synchronized Element getQuiet(Serializable key) throws IllegalStateException, CacheException {
577 return getQuiet((Object) key);
578 }
579
580 /**
581 * Gets an element from the cache, without updating Element statistics. Cache statistics are
582 * still updated.
583 * <p/>
584 *
585 * @param key a serializable value
586 * @return the element, or null, if it does not exist.
587 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
588 * @see #isExpired
589 * @since 1.2
590 */
591 public final synchronized Element getQuiet(Object key) throws IllegalStateException, CacheException {
592 checkStatus();
593 Element element;
594
595 element = searchInMemoryStore(key, false);
596 if (element == null && overflowToDisk) {
597 element = searchInDiskStore(key, false);
598 }
599
600 if (element == null) {
601 missCountNotFound++;
602 if (LOG.isTraceEnabled()) {
603 LOG.trace(name + " cache - Miss");
604 }
605 return null;
606 } else {
607 hitCount++;
608 return element;
609 }
610 }
611
612 /**
613 * Returns a list of all elements in the cache, whether or not they are expired.
614 * <p/>
615 * The returned keys are unique and can be considered a set.
616 * <p/>
617 * The List returned is not live. It is a copy.
618 * <p/>
619 * The time taken is O(n). On a single cpu 1.8Ghz P4, approximately 8ms is required
620 * for each 1000 entries.
621 *
622 * @return a list of {@link Object} keys
623 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
624 */
625 public final synchronized List getKeys() throws IllegalStateException, CacheException {
626 checkStatus();
627
628
629
630
631
632 List allKeyList = new ArrayList();
633 List keyList = Arrays.asList(memoryStore.getKeyArray());
634 allKeyList.addAll(keyList);
635 if (overflowToDisk) {
636 Set allKeys = new HashSet();
637
638 allKeys.addAll(keyList);
639 Object[] diskKeys = diskStore.getKeyArray();
640 for (int i = 0; i < diskKeys.length; i++) {
641 Object diskKey = diskKeys[i];
642 if (allKeys.add(diskKey)) {
643
644 allKeyList.add(diskKey);
645 }
646 }
647 }
648 return allKeyList;
649 }
650
651 /**
652 * Returns a list of all elements in the cache. Only keys of non-expired
653 * elements are returned.
654 * <p/>
655 * The returned keys are unique and can be considered a set.
656 * <p/>
657 * The List returned is not live. It is a copy.
658 * <p/>
659 * The time taken is O(n), where n is the number of elements in the cache. On
660 * a 1.8Ghz P4, the time taken is approximately 200ms per 1000 entries. This method
661 * is not syncrhonized, because it relies on a non-live list returned from {@link #getKeys()}
662 * , which is synchronised, and which takes 8ms per 1000 entries. This way
663 * cache liveness is preserved, even if this method is very slow to return.
664 * <p/>
665 * Consider whether your usage requires checking for expired keys. Because
666 * this method takes so long, depending on cache settings, the list could be
667 * quite out of date by the time you get it.
668 *
669 * @return a list of {@link Object} keys
670 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
671 */
672 public final List getKeysWithExpiryCheck() throws IllegalStateException, CacheException {
673 List allKeyList = getKeys();
674
675 ArrayList nonExpiredKeys = new ArrayList(allKeyList.size());
676 int allKeyListSize = allKeyList.size();
677 for (int i = 0; i < allKeyListSize; i++) {
678 Object key = allKeyList.get(i);
679 Element element = getQuiet(key);
680 if (element != null) {
681 nonExpiredKeys.add(key);
682 }
683 }
684 nonExpiredKeys.trimToSize();
685 return nonExpiredKeys;
686 }
687
688
689 /**
690 * Returns a list of all elements in the cache, whether or not they are expired.
691 * <p/>
692 * The returned keys are not unique and may contain duplicates. If the cache is only
693 * using the memory store, the list will be unique. If the disk store is being used
694 * as well, it will likely contain duplicates, because of the internal store design.
695 * <p/>
696 * The List returned is not live. It is a copy.
697 * <p/>
698 * The time taken is O(log n). On a single cpu 1.8Ghz P4, approximately 6ms is required
699 * for 1000 entries and 36 for 50000.
700 * <p/>
701 * This is the fastest getKeys method
702 *
703 * @return a list of {@link Object} keys
704 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
705 */
706 public final synchronized List getKeysNoDuplicateCheck() throws IllegalStateException {
707 checkStatus();
708 ArrayList allKeys = new ArrayList();
709 List memoryKeySet = Arrays.asList(memoryStore.getKeyArray());
710 allKeys.addAll(memoryKeySet);
711 if (overflowToDisk) {
712 List diskKeySet = Arrays.asList(diskStore.getKeyArray());
713 allKeys.addAll(diskKeySet);
714 }
715 return allKeys;
716 }
717
718 private Element searchInMemoryStore(Object key, boolean updateStatistics) {
719 Element element;
720 if (updateStatistics) {
721 element = memoryStore.get(key);
722 } else {
723 element = memoryStore.getQuiet(key);
724 }
725 if (element != null) {
726 if (isExpired(element)) {
727 if (LOG.isDebugEnabled()) {
728 LOG.debug(name + " Memory cache hit, but element expired");
729 }
730 missCountExpired++;
731 remove(key, true, true, false);
732 element = null;
733 } else {
734 memoryStoreHitCount++;
735 }
736 }
737 return element;
738 }
739
740 private Element searchInDiskStore(Object key, boolean updateStatistics) {
741 if (!(key instanceof Serializable)) {
742 return null;
743 }
744 Serializable serializableKey = (Serializable) key;
745 Element element;
746 if (updateStatistics) {
747 element = diskStore.get(serializableKey);
748 } else {
749 element = diskStore.getQuiet(serializableKey);
750 }
751 if (element != null) {
752 if (isExpired(element)) {
753 if (LOG.isDebugEnabled()) {
754 LOG.debug(name + " cache - Disk Store hit, but element expired");
755 }
756 missCountExpired++;
757 remove(key, true, true, false);
758 element = null;
759 } else {
760 diskStoreHitCount++;
761
762 memoryStore.put(element);
763 }
764 }
765 return element;
766 }
767
768 /**
769 * Removes an {@link Element} from the Cache. This also removes it from any
770 * stores it may be in.
771 * <p/>
772 * Also notifies the CacheEventListener after the element was removed, but only if an Element
773 * with the key actually existed.
774 *
775 * @param key
776 * @return true if the element was removed, false if it was not found in the cache
777 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
778 */
779 public final synchronized boolean remove(Serializable key) throws IllegalStateException {
780 return remove((Object) key);
781 }
782
783 /**
784 * Removes an {@link Element} from the Cache. This also removes it from any
785 * stores it may be in.
786 * <p/>
787 * Also notifies the CacheEventListener after the element was removed, but only if an Element
788 * with the key actually existed.
789 *
790 * @param key
791 * @return true if the element was removed, false if it was not found in the cache
792 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
793 * @since 1.2
794 */
795 public final synchronized boolean remove(Object key) throws IllegalStateException {
796 return remove(key, false);
797 }
798
799
800 /**
801 * Removes an {@link Element} from the Cache. This also removes it from any
802 * stores it may be in.
803 * <p/>
804 * Also notifies the CacheEventListener after the element was removed, but only if an Element
805 * with the key actually existed.
806 *
807 * @param key
808 * @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
809 * further notification to doNotNotifyCacheReplicators cache peers
810 * @return true if the element was removed, false if it was not found in the cache
811 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
812 * @noinspection SameParameterValue
813 */
814 public final synchronized boolean remove(Serializable key, boolean doNotNotifyCacheReplicators) throws IllegalStateException {
815 return remove((Object) key, doNotNotifyCacheReplicators);
816 }
817
818 /**
819 * Removes an {@link Element} from the Cache. This also removes it from any
820 * stores it may be in.
821 * <p/>
822 * Also notifies the CacheEventListener after the element was removed, but only if an Element
823 * with the key actually existed.
824 *
825 * @param key
826 * @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
827 * further notification to doNotNotifyCacheReplicators cache peers
828 * @return true if the element was removed, false if it was not found in the cache
829 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
830 */
831 public final synchronized boolean remove(Object key, boolean doNotNotifyCacheReplicators) throws IllegalStateException {
832 return remove(key, false, true, doNotNotifyCacheReplicators);
833 }
834
835 /**
836 * Removes an {@link Element} from the Cache, without notifying listeners. This also removes it from any
837 * stores it may be in.
838 * <p/>
839 *
840 * @param key
841 * @return true if the element was removed, false if it was not found in the cache
842 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
843 */
844 public final synchronized boolean removeQuiet(Serializable key) throws IllegalStateException {
845 return remove(key, false, false, false);
846 }
847
848 /**
849 * Removes an {@link Element} from the Cache, without notifying listeners. This also removes it from any
850 * stores it may be in.
851 * <p/>
852 *
853 * @param key
854 * @return true if the element was removed, false if it was not found in the cache
855 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
856 * @since 1.2
857 */
858 public final synchronized boolean removeQuiet(Object key) throws IllegalStateException {
859 return remove(key, false, false, false);
860 }
861
862
863 /**
864 * Expires an {@link Element} from the Cache after an attempt to get it determined that it should be expired.
865 * This also removes it from any stores it may be in.
866 * <p/>
867 * Also notifies the CacheEventListener after the element has expired, but only if an Element
868 * with the key actually existed.
869 *
870 * @param key
871 * @param expiry if the reason this method is being called is to expire the element
872 * @param notifyListeners whether to notify listeners
873 * @param doNotNotifyCacheReplicators whether not to notify cache replicators
874 * @return true if the element was removed, false if it was not found in the cache
875 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
876 */
877 private synchronized boolean remove(Object key, boolean expiry, boolean notifyListeners,
878 boolean doNotNotifyCacheReplicators)
879 throws IllegalStateException, CacheException {
880 checkStatus();
881 boolean removed = false;
882 Element elementFromMemoryStore;
883 elementFromMemoryStore = memoryStore.remove(key);
884 if (elementFromMemoryStore != null) {
885 if (notifyListeners) {
886 if (expiry) {
887 registeredEventListeners.notifyElementExpiry(elementFromMemoryStore, doNotNotifyCacheReplicators);
888 } else {
889 registeredEventListeners.notifyElementRemoved(elementFromMemoryStore, doNotNotifyCacheReplicators);
890 }
891 }
892 removed = true;
893 }
894
895
896 Element elementFromDiskStore = null;
897 if (overflowToDisk) {
898 if (!(key instanceof Serializable)) {
899 return false;
900 }
901 Serializable serializableKey = (Serializable) key;
902 elementFromDiskStore = diskStore.remove(serializableKey);
903 }
904
905 if (elementFromDiskStore != null) {
906 if (expiry) {
907 registeredEventListeners.notifyElementExpiry(elementFromDiskStore, doNotNotifyCacheReplicators);
908 } else {
909 registeredEventListeners.notifyElementRemoved(elementFromDiskStore, doNotNotifyCacheReplicators);
910 }
911 removed = true;
912 }
913
914 return removed;
915 }
916
917
918 /**
919 * Removes all cached items.
920 *
921 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
922 */
923 public final synchronized void removeAll() throws IllegalStateException, IOException, CacheException {
924 checkStatus();
925 memoryStore.removeAll();
926 if (overflowToDisk) {
927 diskStore.removeAll();
928 }
929 }
930
931 /**
932 * Flushes all cache items from memory to auxilliary caches and close the auxilliary caches.
933 * <p/>
934 * Should be invoked only by CacheManager.
935 *
936 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
937 */
938 final synchronized void dispose() throws IllegalStateException {
939 checkStatus();
940 memoryStore.dispose();
941 memoryStore = null;
942 if (overflowToDisk) {
943 diskStore.dispose();
944 diskStore = null;
945 }
946
947 registeredEventListeners.dispose();
948
949 changeStatus(Status.STATUS_SHUTDOWN);
950
951 if (diskPersistent) {
952 removeShutdownHook();
953 }
954 }
955
956
957 /**
958 * Flushes all cache items from memory to the disk store, and from the DiskStore to disk.
959 *
960 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
961 */
962 public final synchronized void flush() throws IllegalStateException, CacheException {
963 checkStatus();
964 try {
965 memoryStore.flush();
966 if (overflowToDisk) {
967 diskStore.flush();
968 }
969 } catch (IOException e) {
970 throw new CacheException("Unable to flush cache: " + name + ". Initial cause was " + e.getMessage(), e);
971 }
972 }
973
974
975 /**
976 * Gets the size of the cache. This is a subtle concept. See below.
977 * <p/>
978 * The size is the number of {@link Element}s in the {@link MemoryStore} plus
979 * the number of {@link Element}s in the {@link DiskStore}.
980 * <p/>
981 * This number is the actual number of elements, including expired elements that have
982 * not been removed.
983 * <p/>
984 * Expired elements are removed from the the memory store when
985 * getting an expired element, or when attempting to spool an expired element to
986 * disk.
987 * <p/>
988 * Expired elements are removed from the disk store when getting an expired element,
989 * or when the expiry thread runs, which is once every five minutes.
990 * <p/>
991 * To get an exact size, which would exclude expired elements, use {@link #getKeysWithExpiryCheck()}.size(),
992 * although see that method for the approximate time that would take.
993 * <p/>
994 * To get a very fast result, use {@link #getKeysNoDuplicateCheck()}.size(). If the disk store
995 * is being used, there will be some duplicates.
996 *
997 * @return The size value
998 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
999 */
1000 public final synchronized int getSize() throws IllegalStateException, CacheException {
1001 checkStatus();
1002
1003
1004 return getKeys().size();
1005 }
1006
1007 /**
1008 * Gets the size of the memory store for this cache
1009 * <p/>
1010 * Warning: This method can be very expensive to run. Allow approximately 1 second
1011 * per 1MB of entries. Running this method could create liveness problems
1012 * because the object lock is held for a long period
1013 * <p/>
1014 *
1015 * @return the approximate size of the memory store in bytes
1016 * @throws IllegalStateException
1017 */
1018 public final synchronized long calculateInMemorySize() throws IllegalStateException, CacheException {
1019 checkStatus();
1020 return memoryStore.getSizeInBytes();
1021 }
1022
1023
1024 /**
1025 * Returns the number of elements in the memory store.
1026 *
1027 * @return the number of elements in the memory store
1028 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1029 */
1030 public final long getMemoryStoreSize() throws IllegalStateException {
1031 checkStatus();
1032 return memoryStore.getSize();
1033 }
1034
1035 /**
1036 * Returns the number of elements in the disk store.
1037 *
1038 * @return the number of elements in the disk store.
1039 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1040 */
1041 public final int getDiskStoreSize() throws IllegalStateException {
1042 checkStatus();
1043 if (overflowToDisk) {
1044 return diskStore.getSize();
1045 } else {
1046 return 0;
1047 }
1048 }
1049
1050 /**
1051 * Gets the status attribute of the Cache.
1052 *
1053 * @return The status value from the Status enum class
1054 */
1055 public final Status getStatus() {
1056 return status;
1057 }
1058
1059
1060 private void checkStatus() throws IllegalStateException {
1061 if (!status.equals(Status.STATUS_ALIVE)) {
1062 throw new IllegalStateException("The " + name + " Cache is not alive.");
1063 }
1064 }
1065
1066
1067 /**
1068 * The number of times a requested item was found in the cache.
1069 *
1070 * @return the number of times a requested item was found in the cache
1071 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1072 */
1073 public final int getHitCount()
1074 throws IllegalStateException {
1075 checkStatus();
1076 return hitCount;
1077 }
1078
1079 /**
1080 * Number of times a requested item was found in the Memory Store.
1081 *
1082 * @return Number of times a requested item was found in the Memory Store.
1083 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1084 */
1085 public final int getMemoryStoreHitCount() throws IllegalStateException {
1086 checkStatus();
1087 return memoryStoreHitCount;
1088 }
1089
1090 /**
1091 * Number of times a requested item was found in the Disk Store.
1092 *
1093 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1094 */
1095 public final int getDiskStoreHitCount() throws IllegalStateException {
1096 checkStatus();
1097 return diskStoreHitCount;
1098 }
1099
1100 /**
1101 * Number of times a requested element was not found in the cache. This
1102 * may be because it expired, in which case this will also be recorded in {@link #getMissCountExpired},
1103 * or because it was simply not there.
1104 *
1105 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1106 */
1107 public final int getMissCountNotFound() throws IllegalStateException {
1108 checkStatus();
1109 return missCountNotFound;
1110 }
1111
1112 /**
1113 * Number of times a requested element was found but was expired.
1114 *
1115 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1116 */
1117 public final int getMissCountExpired() throws IllegalStateException {
1118 checkStatus();
1119 return missCountExpired;
1120 }
1121
1122 /**
1123 * Gets the cache name.
1124 */
1125 public final String getName() {
1126 return name;
1127 }
1128
1129 /**
1130 * Sets the cache name which will name.
1131 *
1132 * @param name the name of the cache. Should not be null.
1133 */
1134 final void setName(String name) {
1135 this.name = name;
1136 }
1137
1138 /**
1139 * Gets timeToIdleSeconds.
1140 */
1141 public final long getTimeToIdleSeconds() {
1142 return timeToIdleSeconds;
1143 }
1144
1145 /**
1146 * Gets timeToLiveSeconds.
1147 */
1148 public final long getTimeToLiveSeconds() {
1149 return timeToLiveSeconds;
1150 }
1151
1152 /**
1153 * Are elements eternal.
1154 */
1155 public final boolean isEternal() {
1156 return eternal;
1157 }
1158
1159 /**
1160 * Does the overflow go to disk.
1161 */
1162 public final boolean isOverflowToDisk() {
1163 return overflowToDisk;
1164 }
1165
1166 /**
1167 * Gets the maximum number of elements to hold in memory.
1168 */
1169 public final int getMaxElementsInMemory() {
1170 return maxElementsInMemory;
1171 }
1172
1173 /**
1174 * The policy used to evict elements from the {@link net.sf.ehcache.store.MemoryStore}.
1175 * This can be one of:
1176 * <ol>
1177 * <li>LRU - least recently used
1178 * <li>LFU - least frequently used
1179 * <li>FIFO - first in first out, the oldest element by creation time
1180 * </ol>
1181 * The default value is LRU
1182 *
1183 * @since 1.2
1184 */
1185 public final MemoryStoreEvictionPolicy getMemoryStoreEvictionPolicy() {
1186 return memoryStoreEvictionPolicy;
1187 }
1188
1189 /**
1190 * Returns a {@link String} representation of {@link Cache}.
1191 */
1192 public final String toString() {
1193 StringBuffer dump = new StringBuffer();
1194
1195 dump.append("[ ")
1196 .append(" name = ").append(name)
1197 .append(" status = ").append(status)
1198 .append(" eternal = ").append(eternal)
1199 .append(" overflowToDisk = ").append(overflowToDisk)
1200 .append(" maxElementsInMemory = ").append(maxElementsInMemory)
1201 .append(" memoryStoreEvictionPolicy = ").append(memoryStoreEvictionPolicy)
1202 .append(" timeToLiveSeconds = ").append(timeToLiveSeconds)
1203 .append(" timeToIdleSeconds = ").append(timeToIdleSeconds)
1204 .append(" diskPersistent = ").append(diskPersistent)
1205 .append(" diskExpiryThreadIntervalSeconds = ").append(diskExpiryThreadIntervalSeconds)
1206 .append(registeredEventListeners)
1207 .append(" hitCount = ").append(hitCount)
1208 .append(" memoryStoreHitCount = ").append(memoryStoreHitCount)
1209 .append(" diskStoreHitCount = ").append(diskStoreHitCount)
1210 .append(" missCountNotFound = ").append(missCountNotFound)
1211 .append(" missCountExpired = ").append(missCountExpired)
1212 .append(" ]");
1213
1214 return dump.toString();
1215 }
1216
1217
1218 /**
1219 * Checks whether this cache element has expired.
1220 * <p/>
1221 * The element is expired if:
1222 * <ol>
1223 * <li> the idle time is non-zero and has elapsed, unless the cache is eternal; or
1224 * <li> the time to live is non-zero and has elapsed, unless the cache is eternal; or
1225 * <li> the value of the element is null.
1226 * </ol>
1227 *
1228 * @return true if it has expired
1229 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1230 * @throws NullPointerException if the element is null
1231 */
1232 public final boolean isExpired(Element element) throws IllegalStateException, NullPointerException {
1233 checkStatus();
1234 boolean expired;
1235 synchronized (element) {
1236 if (!eternal) {
1237 expired = checkExpirationForNotEternal(element);
1238 } else {
1239 expired = false;
1240 }
1241 if (LOG.isDebugEnabled()) {
1242 LOG.debug(getName() + ": Is element with key " + element.getObjectKey() + " expired?: " + expired);
1243 }
1244 return expired;
1245 }
1246 }
1247
1248 private boolean checkExpirationForNotEternal(Element element) {
1249 boolean expired;
1250 long now = System.currentTimeMillis();
1251 long creationTime = element.getCreationTime();
1252 long ageLived = now - creationTime;
1253 long ageToLive = timeToLiveSeconds * MS_PER_SECOND;
1254 long nextToLastAccessTime = element.getNextToLastAccessTime();
1255 long mostRecentTime = Math.max(creationTime, nextToLastAccessTime);
1256 long ageIdled = now - mostRecentTime;
1257 long ageToIdle = timeToIdleSeconds * MS_PER_SECOND;
1258
1259 if (LOG.isTraceEnabled()) {
1260 LOG.trace(element.getObjectKey() + " now: " + now);
1261 LOG.trace(element.getObjectKey() + " Creation Time: " + creationTime
1262 + " Next To Last Access Time: " + nextToLastAccessTime);
1263 LOG.trace(element.getObjectKey() + " mostRecentTime: " + mostRecentTime);
1264 LOG.trace(element.getObjectKey() + " Age to Idle: " + ageToIdle + " Age Idled: " + ageIdled);
1265 }
1266
1267 if (timeToLiveSeconds != 0 && (ageLived > ageToLive)) {
1268 if (LOG.isTraceEnabled()) {
1269 LOG.trace("timeToLiveSeconds exceeded : " + element.getObjectKey());
1270 }
1271 expired = true;
1272 } else if (timeToIdleSeconds != 0 && (ageIdled > ageToIdle)) {
1273 if (LOG.isTraceEnabled()) {
1274 LOG.trace("timeToIdleSeconds exceeded : " + element.getObjectKey());
1275 }
1276 expired = true;
1277 } else {
1278 expired = false;
1279 }
1280 return expired;
1281 }
1282
1283
1284 /**
1285 * Clones a cache. This is only legal if the cache has not been
1286 * initialized. At that point only primitives have been set and no
1287 * {@link net.sf.ehcache.store.LruMemoryStore} or {@link net.sf.ehcache.store.DiskStore} has been created.
1288 * <p/>
1289 * A new, empty, RegisteredEventListeners is created on clone.
1290 * <p/>
1291 *
1292 * @return an object of type {@link Cache}
1293 * @throws CloneNotSupportedException
1294 */
1295 public final Object clone() throws CloneNotSupportedException {
1296 if (!(memoryStore == null && diskStore == null)) {
1297 throw new CloneNotSupportedException("Cannot clone an initialized cache.");
1298 }
1299 Cache copy = (Cache) super.clone();
1300 RegisteredEventListeners registeredEventListenersFromCopy = copy.getCacheEventNotificationService();
1301 if (registeredEventListenersFromCopy == null || registeredEventListenersFromCopy.getCacheEventListeners().size() == 0)
1302 {
1303 copy.registeredEventListeners = new RegisteredEventListeners(copy);
1304 } else {
1305 throw new CloneNotSupportedException("Cannot clone a cache with registered event listeners");
1306 }
1307 return copy;
1308 }
1309
1310 /**
1311 * Gets the internal DiskStore.
1312 *
1313 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1314 */
1315 final DiskStore getDiskStore() throws IllegalStateException {
1316 checkStatus();
1317 return diskStore;
1318 }
1319
1320 /**
1321 * Gets the internal MemoryStore.
1322 *
1323 * @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
1324 */
1325 final MemoryStore getMemoryStore() throws IllegalStateException {
1326 checkStatus();
1327 return memoryStore;
1328 }
1329
1330 /**
1331 * @return true if the cache overflows to disk and the disk is persistent between restarts
1332 */
1333 public final boolean isDiskPersistent() {
1334 return diskPersistent;
1335 }
1336
1337 /**
1338 * @return the interval between runs
1339 * of the expiry thread, where it checks the disk store for expired elements. It is not the
1340 * the timeToLiveSeconds.
1341 */
1342 public final long getDiskExpiryThreadIntervalSeconds() {
1343 return diskExpiryThreadIntervalSeconds;
1344 }
1345
1346 /**
1347 * Use this to access the service in order to register and unregister listeners
1348 *
1349 * @return the RegisteredEventListeners instance for this cache.
1350 */
1351 public final RegisteredEventListeners getCacheEventNotificationService() {
1352 return registeredEventListeners;
1353 }
1354
1355
1356 /**
1357 * Whether an Element is stored in the cache in Memory, indicating a very low cost of retrieval.
1358 *
1359 * @return true if an element matching the key is found in memory
1360 */
1361 public final boolean isElementInMemory(Serializable key) {
1362 return isElementInMemory((Object) key);
1363 }
1364
1365 /**
1366 * Whether an Element is stored in the cache in Memory, indicating a very low cost of retrieval.
1367 *
1368 * @return true if an element matching the key is found in memory
1369 * @since 1.2
1370 */
1371 public final boolean isElementInMemory(Object key) {
1372 return memoryStore.containsKey(key);
1373 }
1374
1375 /**
1376 * Whether an Element is stored in the cache on Disk, indicating a higher cost of retrieval.
1377 *
1378 * @return true if an element matching the key is found in the diskStore
1379 */
1380 public final boolean isElementOnDisk(Serializable key) {
1381 return isElementOnDisk((Object) key);
1382 }
1383
1384 /**
1385 * Whether an Element is stored in the cache on Disk, indicating a higher cost of retrieval.
1386 *
1387 * @return true if an element matching the key is found in the diskStore
1388 * @since 1.2
1389 */
1390 public final boolean isElementOnDisk(Object key) {
1391 if (!(key instanceof Serializable)) {
1392 return false;
1393 }
1394 Serializable serializableKey = (Serializable) key;
1395 return diskStore != null && diskStore.containsKey(serializableKey);
1396 }
1397
1398 /**
1399 * The GUID for this cache instance can be used to determine whether two cache instance references
1400 * are pointing to the same cache.
1401 *
1402 * @return the globally unique identifier for this cache instance. This is guaranteed to be unique.
1403 * @since 1.2
1404 */
1405 public final String getGuid() {
1406 return guid;
1407 }
1408
1409 /**
1410 * Gets the CacheManager managing this cache. For a newly created cache this will be null until
1411 * it has been added to a CacheManager.
1412 *
1413 * @return the manager or null if there is none
1414 */
1415 public final CacheManager getCacheManager() {
1416 return cacheManager;
1417 }
1418
1419 /**
1420 * Package local setter for use by CacheManager
1421 *
1422 * @param cacheManager
1423 */
1424 final void setCacheManager(CacheManager cacheManager) {
1425 this.cacheManager = cacheManager;
1426 }
1427
1428
1429 }