Source code: com/opensymphony/oscache/base/algorithm/AbstractConcurrentReadCache.java
1 /*
2 * Copyright (c) 2002-2003 by OpenSymphony
3 * All rights reserved.
4 */
5 /*
6 File: AbstractConcurrentReadCache
7
8 Written by Doug Lea. Adapted from JDK1.2 HashMap.java and Hashtable.java
9 which carries the following copyright:
10
11 * Copyright 1997 by Sun Microsystems, Inc.,
12 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
13 * All rights reserved.
14 *
15 * This software is the confidential and proprietary information
16 * of Sun Microsystems, Inc. ("Confidential Information"). You
17 * shall not disclose such Confidential Information and shall use
18 * it only in accordance with the terms of the license agreement
19 * you entered into with Sun.
20
21 This class is a modified version of ConcurrentReaderHashMap, which was written
22 by Doug Lea (http://gee.cs.oswego.edu/dl/). The modifications where done
23 by Pyxis Technologies. This is a base class for the OSCache module of the
24 openSymphony project (www.opensymphony.com).
25
26 History:
27 Date Who What
28 28oct1999 dl Created
29 14dec1999 dl jmm snapshot
30 19apr2000 dl use barrierLock
31 12jan2001 dl public release
32 Oct2001 abergevin@pyxis-tech.com
33 Integrated persistence and outer algorithm support
34 */
35 package com.opensymphony.oscache.base.algorithm;
36
37
38 /** OpenSymphony BEGIN */
39 import com.opensymphony.oscache.base.CacheEntry;
40 import com.opensymphony.oscache.base.persistence.CachePersistenceException;
41 import com.opensymphony.oscache.base.persistence.PersistenceListener;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45
46 import java.io.IOException;
47 import java.io.Serializable;
48
49 import java.util.*;
50
51 /**
52 * A version of Hashtable that supports mostly-concurrent reading, but exclusive writing.
53 * Because reads are not limited to periods
54 * without writes, a concurrent reader policy is weaker than a classic
55 * reader/writer policy, but is generally faster and allows more
56 * concurrency. This class is a good choice especially for tables that
57 * are mainly created by one thread during the start-up phase of a
58 * program, and from then on, are mainly read (with perhaps occasional
59 * additions or removals) in many threads. If you also need concurrency
60 * among writes, consider instead using ConcurrentHashMap.
61 * <p>
62 *
63 * Successful retrievals using get(key) and containsKey(key) usually
64 * run without locking. Unsuccessful ones (i.e., when the key is not
65 * present) do involve brief synchronization (locking). Also, the
66 * size and isEmpty methods are always synchronized.
67 *
68 * <p> Because retrieval operations can ordinarily overlap with
69 * writing operations (i.e., put, remove, and their derivatives),
70 * retrievals can only be guaranteed to return the results of the most
71 * recently <em>completed</em> operations holding upon their
72 * onset. Retrieval operations may or may not return results
73 * reflecting in-progress writing operations. However, the retrieval
74 * operations do always return consistent results -- either those
75 * holding before any single modification or after it, but never a
76 * nonsense result. For aggregate operations such as putAll and
77 * clear, concurrent reads may reflect insertion or removal of only
78 * some entries. In those rare contexts in which you use a hash table
79 * to synchronize operations across threads (for example, to prevent
80 * reads until after clears), you should either encase operations
81 * in synchronized blocks, or instead use java.util.Hashtable.
82 *
83 * <p>
84 *
85 * This class also supports optional guaranteed
86 * exclusive reads, simply by surrounding a call within a synchronized
87 * block, as in <br>
88 * <code>AbstractConcurrentReadCache t; ... Object v; <br>
89 * synchronized(t) { v = t.get(k); } </code> <br>
90 *
91 * But this is not usually necessary in practice. For
92 * example, it is generally inefficient to write:
93 *
94 * <pre>
95 * AbstractConcurrentReadCache t; ... // Inefficient version
96 * Object key; ...
97 * Object value; ...
98 * synchronized(t) {
99 * if (!t.containsKey(key))
100 * t.put(key, value);
101 * // other code if not previously present
102 * }
103 * else {
104 * // other code if it was previously present
105 * }
106 * }
107 *</pre>
108 * Instead, just take advantage of the fact that put returns
109 * null if the key was not previously present:
110 * <pre>
111 * AbstractConcurrentReadCache t; ... // Use this instead
112 * Object key; ...
113 * Object value; ...
114 * Object oldValue = t.put(key, value);
115 * if (oldValue == null) {
116 * // other code if not previously present
117 * }
118 * else {
119 * // other code if it was previously present
120 * }
121 *</pre>
122 * <p>
123 *
124 * Iterators and Enumerations (i.e., those returned by
125 * keySet().iterator(), entrySet().iterator(), values().iterator(),
126 * keys(), and elements()) return elements reflecting the state of the
127 * hash table at some point at or since the creation of the
128 * iterator/enumeration. They will return at most one instance of
129 * each element (via next()/nextElement()), but might or might not
130 * reflect puts and removes that have been processed since they were
131 * created. They do <em>not</em> throw ConcurrentModificationException.
132 * However, these iterators are designed to be used by only one
133 * thread at a time. Sharing an iterator across multiple threads may
134 * lead to unpredictable results if the table is being concurrently
135 * modified. Again, you can ensure interference-free iteration by
136 * enclosing the iteration in a synchronized block. <p>
137 *
138 * This class may be used as a direct replacement for any use of
139 * java.util.Hashtable that does not depend on readers being blocked
140 * during updates. Like Hashtable but unlike java.util.HashMap,
141 * this class does NOT allow <tt>null</tt> to be used as a key or
142 * value. This class is also typically faster than ConcurrentHashMap
143 * when there is usually only one thread updating the table, but
144 * possibly many retrieving values from it.
145 * <p>
146 *
147 * Implementation note: A slightly faster implementation of
148 * this class will be possible once planned Java Memory Model
149 * revisions are in place.
150 *
151 * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
152 **/
153 public abstract class AbstractConcurrentReadCache extends AbstractMap implements Map, Cloneable, Serializable {
154 /**
155 * The default initial number of table slots for this table (32).
156 * Used when not otherwise specified in constructor.
157 **/
158 public static int DEFAULT_INITIAL_CAPACITY = 32;
159
160 /**
161 * The minimum capacity.
162 * Used if a lower value is implicitly specified
163 * by either of the constructors with arguments.
164 * MUST be a power of two.
165 */
166 private static final int MINIMUM_CAPACITY = 4;
167
168 /**
169 * The maximum capacity.
170 * Used if a higher value is implicitly specified
171 * by either of the constructors with arguments.
172 * MUST be a power of two <= 1<<30.
173 */
174 private static final int MAXIMUM_CAPACITY = 1 << 30;
175
176 /**
177 * The default load factor for this table.
178 * Used when not otherwise specified in constructor, the default is 0.75f.
179 **/
180 public static final float DEFAULT_LOAD_FACTOR = 0.75f;
181
182 //OpenSymphony BEGIN (pretty long!)
183 protected static final String NULL = "_nul!~";
184 protected static Log log = LogFactory.getLog(AbstractConcurrentReadCache.class);
185
186 /*
187 The basic strategy is an optimistic-style scheme based on
188 the guarantee that the hash table and its lists are always
189 kept in a consistent enough state to be read without locking:
190
191 * Read operations first proceed without locking, by traversing the
192 apparently correct list of the apparently correct bin. If an
193 entry is found, but not invalidated (value field null), it is
194 returned. If not found, operations must recheck (after a memory
195 barrier) to make sure they are using both the right list and
196 the right table (which can change under resizes). If
197 invalidated, reads must acquire main update lock to wait out
198 the update, and then re-traverse.
199
200 * All list additions are at the front of each bin, making it easy
201 to check changes, and also fast to traverse. Entry next
202 pointers are never assigned. Remove() builds new nodes when
203 necessary to preserve this.
204
205 * Remove() (also clear()) invalidates removed nodes to alert read
206 operations that they must wait out the full modifications.
207
208 */
209
210 /**
211 * Lock used only for its memory effects. We use a Boolean
212 * because it is serializable, and we create a new one because
213 * we need a unique object for each cache instance.
214 **/
215 protected final Boolean barrierLock = new Boolean(true);
216
217 /**
218 * field written to only to guarantee lock ordering.
219 **/
220 protected transient Object lastWrite;
221
222 /**
223 * The hash table data.
224 */
225 protected transient Entry[] table;
226
227 /**
228 * The total number of mappings in the hash table.
229 */
230 protected transient int count;
231
232 /**
233 * Persistence listener.
234 */
235 protected PersistenceListener persistenceListener = null;
236
237 /**
238 * Use memory cache or not.
239 */
240 protected boolean memoryCaching = true;
241
242 /**
243 * Use unlimited disk caching.
244 */
245 protected boolean unlimitedDiskCache = false;
246
247 /**
248 * The load factor for the hash table.
249 *
250 * @serial
251 */
252 protected float loadFactor;
253
254 /**
255 * Default cache capacity (number of entries).
256 */
257 protected final int DEFAULT_MAX_ENTRIES = 100;
258
259 /**
260 * Max number of element in cache when considered unlimited.
261 */
262 protected final int UNLIMITED = 2147483646;
263 protected transient Collection values = null;
264
265 /**
266 * A HashMap containing the group information.
267 * Each entry uses the group name as the key, and holds a
268 * <code>Set</code> of containing keys of all
269 * the cache entries that belong to that particular group.
270 */
271 protected HashMap groups = new HashMap();
272 protected transient Set entrySet = null;
273
274 // Views
275 protected transient Set keySet = null;
276
277 /**
278 * Cache capacity (number of entries).
279 */
280 protected int maxEntries = DEFAULT_MAX_ENTRIES;
281
282 /**
283 * The table is rehashed when its size exceeds this threshold.
284 * (The value of this field is always (int)(capacity * loadFactor).)
285 *
286 * @serial
287 */
288 protected int threshold;
289
290 /**
291 * Use overflow persistence caching.
292 */
293 private boolean overflowPersistence = false;
294
295 /**
296 * Constructs a new, empty map with the specified initial capacity and the specified load factor.
297 *
298 * @param initialCapacity the initial capacity
299 * The actual initial capacity is rounded to the nearest power of two.
300 * @param loadFactor the load factor of the AbstractConcurrentReadCache
301 * @throws IllegalArgumentException if the initial maximum number
302 * of elements is less
303 * than zero, or if the load factor is nonpositive.
304 */
305 public AbstractConcurrentReadCache(int initialCapacity, float loadFactor) {
306 if (loadFactor <= 0) {
307 throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
308 }
309
310 this.loadFactor = loadFactor;
311
312 int cap = p2capacity(initialCapacity);
313 table = new Entry[cap];
314 threshold = (int) (cap * loadFactor);
315 }
316
317 /**
318 * Constructs a new, empty map with the specified initial capacity and default load factor.
319 *
320 * @param initialCapacity the initial capacity of the
321 * AbstractConcurrentReadCache.
322 * @throws IllegalArgumentException if the initial maximum number
323 * of elements is less
324 * than zero.
325 */
326 public AbstractConcurrentReadCache(int initialCapacity) {
327 this(initialCapacity, DEFAULT_LOAD_FACTOR);
328 }
329
330 /**
331 * Constructs a new, empty map with a default initial capacity and load factor.
332 */
333 public AbstractConcurrentReadCache() {
334 this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
335 }
336
337 /**
338 * Constructs a new map with the same mappings as the given map.
339 * The map is created with a capacity of twice the number of mappings in
340 * the given map or 11 (whichever is greater), and a default load factor.
341 */
342 public AbstractConcurrentReadCache(Map t) {
343 this(Math.max(2 * t.size(), 11), DEFAULT_LOAD_FACTOR);
344 putAll(t);
345 }
346
347 /**
348 * Returns <tt>true</tt> if this map contains no key-value mappings.
349 *
350 * @return <tt>true</tt> if this map contains no key-value mappings.
351 */
352 public synchronized boolean isEmpty() {
353 return count == 0;
354 }
355
356 /**
357 * Returns a set of the cache keys that reside in a particular group.
358 *
359 * @param groupName The name of the group to retrieve.
360 * @return a set containing all of the keys of cache entries that belong
361 * to this group, or <code>null</code> if the group was not found.
362 * @exception NullPointerException if the groupName is <code>null</code>.
363 */
364 public Set getGroup(String groupName) {
365 if (log.isDebugEnabled()) {
366 log.debug("getGroup called (group=" + groupName + ")");
367 }
368
369 Set groupEntries = null;
370
371 if (memoryCaching && (groups != null)) {
372 groupEntries = (Set) getGroupForReading(groupName);
373 }
374
375 if (groupEntries == null) {
376 // Not in the map, try the persistence layer
377 groupEntries = persistRetrieveGroup(groupName);
378 }
379
380 return groupEntries;
381 }
382
383 /**
384 * Set the cache capacity
385 */
386 public void setMaxEntries(int newLimit) {
387 if (newLimit > 0) {
388 maxEntries = newLimit;
389
390 synchronized (this) { // because remove() isn't synchronized
391
392 while (size() > maxEntries) {
393 remove(removeItem(), false);
394 }
395 }
396 } else {
397 // Capacity must be at least 1
398 throw new IllegalArgumentException("Cache maximum number of entries must be at least 1");
399 }
400 }
401
402 /**
403 * Retrieve the cache capacity (number of entries).
404 */
405 public int getMaxEntries() {
406 return maxEntries;
407 }
408
409 /**
410 * Sets the memory caching flag.
411 */
412 public void setMemoryCaching(boolean memoryCaching) {
413 this.memoryCaching = memoryCaching;
414 }
415
416 /**
417 * Check if memory caching is used.
418 */
419 public boolean isMemoryCaching() {
420 return memoryCaching;
421 }
422
423 /**
424 * Set the persistence listener to use.
425 */
426 public void setPersistenceListener(PersistenceListener listener) {
427 this.persistenceListener = listener;
428 }
429
430 /**
431 * Get the persistence listener.
432 */
433 public PersistenceListener getPersistenceListener() {
434 return persistenceListener;
435 }
436
437 /**
438 * Sets the unlimited disk caching flag.
439 */
440 public void setUnlimitedDiskCache(boolean unlimitedDiskCache) {
441 this.unlimitedDiskCache = unlimitedDiskCache;
442 }
443
444 /**
445 * Check if we use unlimited disk cache.
446 */
447 public boolean isUnlimitedDiskCache() {
448 return unlimitedDiskCache;
449 }
450
451 /**
452 * Check if we use overflowPersistence
453 *
454 * @return Returns the overflowPersistence.
455 */
456 public boolean isOverflowPersistence() {
457 return this.overflowPersistence;
458 }
459
460 /**
461 * Sets the overflowPersistence flag
462 *
463 * @param overflowPersistence The overflowPersistence to set.
464 */
465 public void setOverflowPersistence(boolean overflowPersistence) {
466 this.overflowPersistence = overflowPersistence;
467 }
468
469 /**
470 * Return the number of slots in this table.
471 **/
472 public synchronized int capacity() {
473 return table.length;
474 }
475
476 /**
477 * Removes all mappings from this map.
478 */
479 public synchronized void clear() {
480 Entry[] tab = table;
481
482 for (int i = 0; i < tab.length; ++i) {
483 // must invalidate all to force concurrent get's to wait and then retry
484 for (Entry e = tab[i]; e != null; e = e.next) {
485 e.value = null;
486
487 /** OpenSymphony BEGIN */
488 itemRemoved(e.key);
489
490 /** OpenSymphony END */
491 }
492
493 tab[i] = null;
494 }
495
496 // Clean out the entire disk cache
497 persistClear();
498
499 count = 0;
500 recordModification(tab);
501 }
502
503 /**
504 * Returns a shallow copy of this.
505 * <tt>AbstractConcurrentReadCache</tt> instance: the keys and
506 * values themselves are not cloned.
507 *
508 * @return a shallow copy of this map.
509 */
510 public synchronized Object clone() {
511 try {
512 AbstractConcurrentReadCache t = (AbstractConcurrentReadCache) super.clone();
513 t.keySet = null;
514 t.entrySet = null;
515 t.values = null;
516
517 Entry[] tab = table;
518 t.table = new Entry[tab.length];
519
520 Entry[] ttab = t.table;
521
522 for (int i = 0; i < tab.length; ++i) {
523 Entry first = tab[i];
524
525 if (first != null) {
526 ttab[i] = (Entry) (first.clone());
527 }
528 }
529
530 return t;
531 } catch (CloneNotSupportedException e) {
532 // this shouldn't happen, since we are Cloneable
533 throw new InternalError();
534 }
535 }
536
537 /**
538 * Tests if some key maps into the specified value in this table.
539 * This operation is more expensive than the <code>containsKey</code>
540 * method.<p>
541 *
542 * Note that this method is identical in functionality to containsValue,
543 * (which is part of the Map interface in the collections framework).
544 *
545 * @param value a value to search for.
546 * @return <code>true</code> if and only if some key maps to the
547 * <code>value</code> argument in this table as
548 * determined by the <tt>equals</tt> method;
549 * <code>false</code> otherwise.
550 * @exception NullPointerException if the value is <code>null</code>.
551 * @see #containsKey(Object)
552 * @see #containsValue(Object)
553 * @see Map
554 */
555 public boolean contains(Object value) {
556 return containsValue(value);
557 }
558
559 /**
560 * Tests if the specified object is a key in this table.
561 *
562 * @param key possible key.
563 * @return <code>true</code> if and only if the specified object
564 * is a key in this table, as determined by the
565 * <tt>equals</tt> method; <code>false</code> otherwise.
566 * @exception NullPointerException if the key is
567 * <code>null</code>.
568 * @see #contains(Object)
569 */
570 public boolean containsKey(Object key) {
571 return get(key) != null;
572
573 /** OpenSymphony BEGIN */
574
575 // TODO: Also check the persistence?
576
577 /** OpenSymphony END */
578 }
579
580 /**
581 * Returns <tt>true</tt> if this map maps one or more keys to the
582 * specified value. Note: This method requires a full internal
583 * traversal of the hash table, and so is much slower than
584 * method <tt>containsKey</tt>.
585 *
586 * @param value value whose presence in this map is to be tested.
587 * @return <tt>true</tt> if this map maps one or more keys to the
588 * specified value.
589 * @exception NullPointerException if the value is <code>null</code>.
590 */
591 public boolean containsValue(Object value) {
592 if (value == null) {
593 throw new NullPointerException();
594 }
595
596 Entry[] tab = getTableForReading();
597
598 for (int i = 0; i < tab.length; ++i) {
599 for (Entry e = tab[i]; e != null; e = e.next) {
600 Object v = e.value;
601
602 if ((v != null) && value.equals(v)) {
603 return true;
604 }
605 }
606 }
607
608 return false;
609 }
610
611 /**
612 * Returns an enumeration of the values in this table.
613 * Use the Enumeration methods on the returned object to fetch the elements
614 * sequentially.
615 *
616 * @return an enumeration of the values in this table.
617 * @see java.util.Enumeration
618 * @see #keys()
619 * @see #values()
620 * @see Map
621 */
622 public Enumeration elements() {
623 return new ValueIterator();
624 }
625
626 /**
627 * Returns a collection view of the mappings contained in this map.
628 * Each element in the returned collection is a <tt>Map.Entry</tt>. The
629 * collection is backed by the map, so changes to the map are reflected in
630 * the collection, and vice-versa. The collection supports element
631 * removal, which removes the corresponding mapping from the map, via the
632 * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
633 * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
634 * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
635 *
636 * @return a collection view of the mappings contained in this map.
637 */
638 public Set entrySet() {
639 Set es = entrySet;
640
641 if (es != null) {
642 return es;
643 } else {
644 return entrySet = new AbstractSet() {
645 public Iterator iterator() {
646 return new HashIterator();
647 }
648
649 public boolean contains(Object o) {
650 if (!(o instanceof Map.Entry)) {
651 return false;
652 }
653
654 Map.Entry entry = (Map.Entry) o;
655 Object key = entry.getKey();
656 Object v = AbstractConcurrentReadCache.this.get(key);
657
658 return (v != null) && v.equals(entry.getValue());
659 }
660
661 public boolean remove(Object o) {
662 if (!(o instanceof Map.Entry)) {
663 return false;
664 }
665
666 return AbstractConcurrentReadCache.this.findAndRemoveEntry((Map.Entry) o);
667 }
668
669 public int size() {
670 return AbstractConcurrentReadCache.this.size();
671 }
672
673 public void clear() {
674 AbstractConcurrentReadCache.this.clear();
675 }
676 };
677 }
678 }
679
680 /**
681 * Returns the value to which the specified key is mapped in this table.
682 *
683 * @param key a key in the table.
684 * @return the value to which the key is mapped in this table;
685 * <code>null</code> if the key is not mapped to any value in
686 * this table.
687 * @exception NullPointerException if the key is
688 * <code>null</code>.
689 * @see #put(Object, Object)
690 */
691 public Object get(Object key) {
692 if (log.isDebugEnabled()) {
693 log.debug("get called (key=" + key + ")");
694 }
695
696 // throw null pointer exception if key null
697 int hash = hash(key);
698
699 /*
700 Start off at the apparently correct bin. If entry is found, we
701 need to check after a barrier anyway. If not found, we need a
702 barrier to check if we are actually in right bin. So either
703 way, we encounter only one barrier unless we need to retry.
704 And we only need to fully synchronize if there have been
705 concurrent modifications.
706 */
707 Entry[] tab = table;
708 int index = hash & (tab.length - 1);
709 Entry first = tab[index];
710 Entry e = first;
711
712 for (;;) {
713 if (e == null) {
714 // If key apparently not there, check to
715 // make sure this was a valid read
716 tab = getTableForReading();
717
718 if (first == tab[index]) {
719 /** OpenSymphony BEGIN */
720
721 /* Previous code
722 return null;*/
723
724 // Not in the table, try persistence
725 Object value = persistRetrieve(key);
726
727 if (value != null) {
728 // Update the map, but don't persist the data
729 put(key, value, false);
730 }
731
732 return value;
733
734 /** OpenSymphony END */
735 } else {
736 // Wrong list -- must restart traversal at new first
737 e = first = tab[index = hash & (tab.length - 1)];
738 }
739 }
740 // checking for pointer equality first wins in most applications
741 else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
742 Object value = e.value;
743
744 if (value != null) {
745 /** OpenSymphony BEGIN */
746
747 /* Previous code
748 return value;*/
749 if (NULL.equals(value)) {
750 // Memory cache disable, use disk
751 value = persistRetrieve(e.key);
752
753 if (value != null) {
754 itemRetrieved(key);
755 }
756
757 return value; // fix [CACHE-13]
758 } else {
759 itemRetrieved(key);
760
761 return value;
762 }
763
764 /** OpenSymphony END */
765 }
766
767 // Entry was invalidated during deletion. But it could
768 // have been re-inserted, so we must retraverse.
769 // To avoid useless contention, get lock to wait out modifications
770 // before retraversing.
771 synchronized (this) {
772 tab = table;
773 }
774
775 e = first = tab[index = hash & (tab.length - 1)];
776 } else {
777 e = e.next;
778 }
779 }
780 }
781
782 /**
783 * Returns a set view of the keys contained in this map.
784 * The set is backed by the map, so changes to the map are reflected in the set, and
785 * vice-versa. The set supports element removal, which removes the
786 * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
787 * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
788 * <tt>clear</tt> operations. It does not support the <tt>add</tt> or
789 * <tt>addAll</tt> operations.
790 *
791 * @return a set view of the keys contained in this map.
792 */
793 public Set keySet() {
794 Set ks = keySet;
795
796 if (ks != null) {
797 return ks;
798 } else {
799 return keySet = new AbstractSet() {
800 public Iterator iterator() {
801 return new KeyIterator();
802 }
803
804 public int size() {
805 return AbstractConcurrentReadCache.this.size();
806 }
807
808 public boolean contains(Object o) {
809 return AbstractConcurrentReadCache.this.containsKey(o);
810 }
811
812 public boolean remove(Object o) {
813 return AbstractConcurrentReadCache.this.remove(o) != null;
814 }
815
816 public void clear() {
817 AbstractConcurrentReadCache.this.clear();
818 }
819 };
820 }
821 }
822
823 /**
824 * Returns an enumeration of the keys in this table.
825 *
826 * @return an enumeration of the keys in this table.
827 * @see Enumeration
828 * @see #elements()
829 * @see #keySet()
830 * @see Map
831 */
832 public Enumeration keys() {
833 return new KeyIterator();
834 }
835
836 /**
837 * Return the load factor
838 **/
839 public float loadFactor() {
840 return loadFactor;
841 }
842
843 /**
844 * Maps the specified <code>key</code> to the specified <code>value</code> in this table.
845 * Neither the key nor the
846 * value can be <code>null</code>. <p>
847 *
848 * The value can be retrieved by calling the <code>get</code> method
849 * with a key that is equal to the original key.
850 *
851 * @param key the table key.
852 * @param value the value.
853 * @return the previous value of the specified key in this table,
854 * or <code>null</code> if it did not have one.
855 * @exception NullPointerException if the key or value is
856 * <code>null</code>.
857 * @see Object#equals(Object)
858 * @see #get(Object)
859 */
860 /** OpenSymphony BEGIN */
861 public Object put(Object key, Object value) {
862 // Call the internal put using persistance
863 return put(key, value, true);
864 }
865
866 /**
867 * Copies all of the mappings from the specified map to this one.
868 *
869 * These mappings replace any mappings that this map had for any of the
870 * keys currently in the specified Map.
871 *
872 * @param t Mappings to be stored in this map.
873 */
874 public synchronized void putAll(Map t) {
875 for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
876 Map.Entry entry = (Map.Entry) it.next();
877 Object key = entry.getKey();
878 Object value = entry.getValue();
879 put(key, value);
880 }
881 }
882
883 /**
884 * Removes the key (and its corresponding value) from this table.
885 * This method does nothing if the key is not in the table.
886 *
887 * @param key the key that needs to be removed.
888 * @return the value to which the key had been mapped in this table,
889 * or <code>null</code> if the key did not have a mapping.
890 * @exception NullPointerException if the key is
891 * <code>null</code>.
892 */
893 /** OpenSymphony BEGIN */
894 public Object remove(Object key) {
895 return remove(key, true);
896 }
897
898 /**
899 * Returns the total number of cache entries held in this map.
900 *
901 * @return the number of key-value mappings in this map.
902 */
903 public synchronized int size() {
904 return count;
905 }
906
907 /**
908 * Returns a collection view of the values contained in this map.
909 * The collection is backed by the map, so changes to the map are reflected in
910 * the collection, and vice-versa. The collection supports element
911 * removal, which removes the corresponding mapping from this map, via the
912 * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
913 * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
914 * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
915 *
916 * @return a collection view of the values contained in this map.
917 */
918 public Collection values() {
919 Collection vs = values;
920
921 if (vs != null) {
922 return vs;
923 } else {
924 return values = new AbstractCollection() {
925 public Iterator iterator() {
926 return new ValueIterator();
927 }
928
929 public int size() {
930 return AbstractConcurrentReadCache.this.size();
931 }
932
933 public boolean contains(Object o) {
934 return AbstractConcurrentReadCache.this.containsValue(o);
935 }
936
937 public void clear() {
938 AbstractConcurrentReadCache.this.clear();
939 }
940 };
941 }
942 }
943
944 /**
945 * Get ref to group.
946 * CACHE-127 Synchronized copying of the group entry set since
947 * the new HashSet(Collection c) constructor uses the iterator.
948 * This may slow things down but it is better than a
949 * ConcurrentModificationException. We might have to revisit the
950 * code if performance is too adversely impacted.
951 **/
952 protected synchronized final Set getGroupForReading(String groupName) {
953 Set group = (Set) getGroupsForReading().get(groupName);
954
955 if (group == null) {
956 return null;
957 }
958
959 return new HashSet(group);
960 }
961
962 /**
963 * Get ref to groups.
964 * The reference and the cells it
965 * accesses will be at least as fresh as from last
966 * use of barrierLock
967 **/
968 protected final Map getGroupsForReading() {
969 synchronized (barrierLock) {
970 return groups;
971 }
972 }
973
974 /**
975 * Get ref to table; the reference and the cells it
976 * accesses will be at least as fresh as from last
977 * use of barrierLock
978 **/
979 protected final Entry[] getTableForReading() {
980 synchronized (barrierLock) {
981 return table;
982 }
983 }
984
985 /**
986 * Force a memory synchronization that will cause
987 * all readers to see table. Call only when already
988 * holding main synch lock.
989 **/
990 protected final void recordModification(Object x) {
991 synchronized (barrierLock) {
992 lastWrite = x;
993 }
994 }
995
996 /**
997 * Helper method for entrySet remove.
998 **/
999 protected synchronized boolean findAndRemoveEntry(Map.Entry entry) {
1000 Object key = entry.getKey();
1001 Object v = get(key);
1002
1003 if ((v != null) && v.equals(entry.getValue())) {
1004 remove(key);
1005
1006 return true;
1007 } else {
1008 return false;
1009 }
1010 }
1011
1012 /**
1013 * Remove an object from the persistence.
1014 * @param key The key of the object to remove
1015 */
1016 protected void persistRemove(Object key) {
1017 if (log.isDebugEnabled()) {
1018 log.debug("PersistRemove called (key=" + key + ")");
1019 }
1020
1021 if (persistenceListener != null) {
1022 try {
1023 persistenceListener.remove((String) key);
1024 } catch (CachePersistenceException e) {
1025 log.error("[oscache] Exception removing cache entry with key '" + key + "' from persistence", e);
1026 }
1027 }
1028 }
1029
1030 /**
1031 * Removes a cache group using the persistence listener.
1032 * @param groupName The name of the group to remove
1033 */
1034 protected void persistRemoveGroup(String groupName) {
1035 if (log.isDebugEnabled()) {
1036 log.debug("persistRemoveGroup called (groupName=" + groupName + ")");
1037 }
1038
1039 if (persistenceListener != null) {
1040 try {
1041 persistenceListener.removeGroup(groupName);
1042 } catch (CachePersistenceException e) {
1043 log.error("[oscache] Exception removing group " + groupName, e);
1044 }
1045 }
1046 }
1047
1048 /**
1049 * Retrieve an object from the persistence listener.
1050 * @param key The key of the object to retrieve
1051 */
1052 protected Object persistRetrieve(Object key) {
1053 if (log.isDebugEnabled()) {
1054 log.debug("persistRetrieve called (key=" + key + ")");
1055 }
1056
1057 Object entry = null;
1058
1059 if (persistenceListener != null) {
1060 try {
1061 entry = persistenceListener.retrieve((String) key);
1062 } catch (CachePersistenceException e) {
1063 /**
1064 * It is normal that we get an exception occasionally.
1065 * It happens when the item is invalidated (written or removed)
1066 * during read. The logic is constructed so that read is retried.
1067 */
1068 }
1069 }
1070
1071 return entry;
1072 }
1073
1074 /**
1075 * Retrieves a cache group using the persistence listener.
1076 * @param groupName The name of the group to retrieve
1077 */
1078 protected Set persistRetrieveGroup(String groupName) {
1079 if (log.isDebugEnabled()) {
1080 log.debug("persistRetrieveGroup called (groupName=" + groupName + ")");
1081 }
1082
1083 if (persistenceListener != null) {
1084 try {
1085 return persistenceListener.retrieveGroup(groupName);
1086 } catch (CachePersistenceException e) {
1087 log.error("[oscache] Exception retrieving group " + groupName, e);
1088 }
1089 }
1090
1091 return null;
1092 }
1093
1094 /**
1095 * Store an object in the cache using the persistence listener.
1096 * @param key The object key
1097 * @param obj The object to store
1098 */
1099 protected void persistStore(Object key, Object obj) {
1100 if (log.isDebugEnabled()) {
1101 log.debug("persistStore called (key=" + key + ")");
1102 }
1103
1104 if (persistenceListener != null) {
1105 try {
1106 persistenceListener.store((String) key, obj);
1107 } catch (CachePersistenceException e) {
1108 log.error("[oscache] Exception persisting " + key, e);
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Creates or Updates a cache group using the persistence listener.
1115 * @param groupName The name of the group to update
1116 * @param group The entries for the group
1117 */
1118 protected void persistStoreGroup(String groupName, Set group) {
1119 if (log.isDebugEnabled()) {
1120 log.debug("persistStoreGroup called (groupName=" + groupName + ")");
1121 }
1122
1123 if (persistenceListener != null) {
1124 try {
1125 if ((group == null) || group.isEmpty()) {
1126 persistenceListener.removeGroup(groupName);
1127 } else {
1128 persistenceListener.storeGroup(groupName, group);
1129 }
1130 } catch (CachePersistenceException e) {
1131 log.error("[oscache] Exception persisting group " + groupName, e);
1132 }
1133 }
1134 }
1135
1136 /**
1137 * Removes the entire cache from persistent storage.
1138 */
1139 protected void persistClear() {
1140 if (log.isDebugEnabled()) {
1141 log.debug("persistClear called");
1142 ;
1143 }
1144
1145 if (persistenceListener != null) {
1146 try {
1147 persistenceListener.clear();
1148 } catch (CachePersistenceException e) {
1149 log.error("[oscache] Exception clearing persistent cache", e);
1150 }
1151 }
1152 }
1153
1154 /**
1155 * Notify the underlying implementation that an item was put in the cache.
1156 *
1157 * @param key The cache key of the item that was put.
1158 */
1159 protected abstract void itemPut(Object key);
1160
1161 /**
1162 * Notify any underlying algorithm that an item has been retrieved from the cache.
1163 *
1164 * @param key The cache key of the item that was retrieved.
1165 */
1166 protected abstract void itemRetrieved(Object key);
1167
1168 /**
1169 * Notify the underlying implementation that an item was removed from the cache.
1170 *
1171 * @param key The cache key of the item that was removed.
1172 */
1173 protected abstract void itemRemoved(Object key);
1174
1175 /**
1176 * The cache has reached its cacpacity and an item needs to be removed.
1177 * (typically according to an algorithm such as LRU or FIFO).
1178 *
1179 * @return The key of whichever item was removed.
1180 */
1181 protected abstract Object removeItem();
1182
1183 /**
1184 * Reconstitute the <tt>AbstractConcurrentReadCache</tt>.
1185 * instance from a stream (i.e.,
1186 * deserialize it).
1187 */
1188 private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
1189 // Read in the threshold, loadfactor, and any hidden stuff
1190 s.defaultReadObject();
1191
1192 // Read in number of buckets and allocate the bucket array;
1193 int numBuckets = s.readInt();
1194 table = new Entry[numBuckets];
1195
1196 // Read in size (number of Mappings)
1197 int size = s.readInt();
1198
1199 // Read the keys and values, and put the mappings in the table
1200 for (int i = 0; i < size; i++) {
1201 Object key = s.readObject();
1202 Object value = s.readObject();
1203 put(key, value);
1204 }
1205 }
1206
1207 /**
1208 * Rehashes the contents of this map into a new table with a larger capacity.
1209 * This method is called automatically when the
1210 * number of keys in this map exceeds its capacity and load factor.
1211 */
1212 protected void rehash() {
1213 Entry[] oldMap = table;
1214 int oldCapacity = oldMap.length;
1215
1216 if (oldCapacity >= MAXIMUM_CAPACITY) {
1217 return;
1218 }
1219
1220 int newCapacity = oldCapacity << 1;
1221 Entry[] newMap = new Entry[newCapacity];
1222 threshold = (int) (newCapacity * loadFactor);
1223
1224 /*
1225 We need to guarantee that any existing reads of oldMap can
1226 proceed. So we cannot yet null out each oldMap bin.
1227
1228 Because we are using power-of-two expansion, the elements
1229 from each bin must either stay at same index, or move
1230 to oldCapacity+index. We also minimize new node creation by
1231 catching cases where old nodes can be reused because their
1232 .next fields won't change. (This is checked only for sequences
1233 of one and two. It is not worth checking longer ones.)
1234 */
1235 for (int i = 0; i < oldCapacity; ++i) {
1236 Entry l = null;
1237 Entry h = null;
1238 Entry e = oldMap[i];
1239
1240 while (e != null) {
1241 int hash = e.hash;
1242 Entry next = e.next;
1243
1244 if ((hash & oldCapacity) == 0) {
1245 // stays at newMap[i]
1246 if (l == null) {
1247 // try to reuse node
1248 if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) == 0))) {
1249 l = e;
1250
1251 break;
1252 }
1253 }
1254
1255 l = new Entry(hash, e.key, e.value, l);
1256 } else {
1257 // moves to newMap[oldCapacity+i]
1258 if (h == null) {
1259 if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) != 0))) {
1260 h = e;
1261
1262 break;
1263 }
1264 }
1265
1266 h = new Entry(hash, e.key, e.value, h);
1267 }
1268
1269 e = next;
1270 }
1271
1272 newMap[i] = l;
1273 newMap[oldCapacity + i] = h;
1274 }
1275
1276 table = newMap;
1277 recordModification(newMap);
1278 }
1279
1280 /**
1281 * Continuation of put(), called only when synch lock is
1282 * held and interference has been detected.
1283 **/
1284 /** OpenSymphony BEGIN */
1285
1286 /* Previous code
1287 protected Object sput(Object key, Object value, int hash) {*/
1288 protected Object sput(Object key, Object value, int hash, boolean persist) {
1289 /** OpenSymphony END */
1290 Entry[] tab = table;
1291 int index = hash & (tab.length - 1);
1292 Entry first = tab[index];
1293 Entry e = first;
1294
1295 for (;;) {
1296 if (e == null) {
1297 /** OpenSymphony BEGIN */
1298
1299 // Previous code
1300 // Entry newEntry = new Entry(hash, key, value, first);
1301 Entry newEntry;
1302
1303 if (memoryCaching) {
1304 newEntry = new Entry(hash, key, value, first);
1305 } else {
1306 newEntry = new Entry(hash, key, NULL, first);
1307 }
1308
1309 itemPut(key);
1310
1311 // Persist if required
1312 if (persist && !overflowPersistence) {
1313 persistStore(key, value);
1314 }
1315
1316 // If we have a CacheEntry, update the group lookups
1317 if (value instanceof CacheEntry) {
1318 updateGroups(null, (CacheEntry) value, persist);
1319 }
1320
1321 /** OpenSymphony END */
1322 tab[index] = newEntry;
1323
1324 if (++count >= threshold) {
1325 rehash();
1326 } else {
1327 recordModification(newEntry);
1328 }
1329
1330 return null;
1331 } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
1332 Object oldValue = e.value;
1333
1334 /** OpenSymphony BEGIN */
1335
1336 /* Previous code
1337 e.value = value; */
1338 if (memoryCaching) {
1339 e.value = value;
1340 }
1341
1342 // Persist if required
1343 if (persist && overflowPersistence) {
1344 persistRemove(key);
1345 } else if (persist) {
1346 persistStore(key, value);
1347 }
1348
1349 updateGroups(oldValue, value, persist);
1350
1351 itemPut(key);
1352
1353 /** OpenSymphony END */
1354 return oldValue;
1355 } else {
1356 e = e.next;
1357 }
1358 }
1359 }
1360
1361 /**
1362 * Continuation of remove(), called only when synch lock is
1363 * held and interference has been detected.
1364 **/
1365 /** OpenSymphony BEGIN */
1366
1367 /* Previous code
1368 protected Object sremove(Object key, int hash) { */
1369 protected Object sremove(Object key, int hash, boolean invokeAlgorithm) {
1370 /** OpenSymphony END */
1371 Entry[] tab = table;
1372 int index = hash & (tab.length - 1);
1373 Entry first = tab[index];
1374 Entry e = first;
1375
1376 for (;;) {
1377 if (e == null) {
1378 return null;
1379 } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
1380 Object oldValue = e.value;
1381 e.value = null;
1382 count--;
1383
1384 /** OpenSymphony BEGIN */
1385 if (!unlimitedDiskCache && !overflowPersistence) {
1386 persistRemove(e.key);
1387 }
1388
1389 if (overflowPersistence && ((size() + 1) >= maxEntries)) {
1390 persistStore(key, oldValue);
1391 }
1392
1393 if (invokeAlgorithm) {
1394 itemRemoved(key);
1395 }
1396
1397 /** OpenSymphony END */
1398 Entry head = e.next;
1399
1400 for (Entry p = first; p != e; p = p.next) {
1401 head = new Entry(p.hash, p.key, p.value, head);
1402 }
1403
1404 tab[index] = head;
1405 recordModification(head);
1406
1407 return oldValue;
1408 } else {
1409 e = e.next;
1410 }
1411 }
1412 }
1413
1414 /**
1415 * Save the state of the <tt>AbstractConcurrentReadCache</tt> instance to a stream.
1416 * (i.e., serialize it).
1417 *
1418 * @serialData The <i>capacity</i> of the
1419 * AbstractConcurrentReadCache (the length of the
1420 * bucket array) is emitted (int), followed by the
1421 * <i>size</i> of the AbstractConcurrentReadCache (the number of key-value
1422 * mappings), followed by the key (Object) and value (Object)
1423 * for each key-value mapping represented by the AbstractConcurrentReadCache
1424 * The key-value mappings are emitted in no particular order.
1425 */
1426 private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {
1427 // Write out the threshold, loadfactor, and any hidden stuff
1428 s.defaultWriteObject();
1429
1430 // Write out number of buckets
1431 s.writeInt(table.length);
1432
1433 // Write out size (number of Mappings)
1434 s.writeInt(count);
1435
1436 // Write out keys and values (alternating)
1437 for (int index = table.length - 1; index >= 0; index--) {
1438 Entry entry = table[index];
1439
1440 while (entry != null) {
1441 s.writeObject(entry.key);
1442 s.writeObject(entry.value);
1443 entry = entry.next;
1444 }
1445 }
1446 }
1447
1448 /**
1449 * Return hash code for Object x.
1450 * Since we are using power-of-two
1451 * tables, it is worth the effort to improve hashcode via
1452 * the same multiplicative scheme as used in IdentityHashMap.
1453 */
1454 private static int hash(Object x) {
1455 int h = x.hashCode();
1456
1457 // Multiply by 127 (quickly, via shifts), and mix in some high
1458 // bits to help guard against bunching of codes that are
1459 // consecutive or equally spaced.
1460 return ((h << 7) - h + (h >>> 9) + (h >>> 17));
1461 }
1462
1463 /**
1464 * Add this cache key to the groups specified groups.
1465 * We have to treat the
1466 * memory and disk group mappings seperately so they remain valid for their
1467 * corresponding memory/disk caches. (eg if mem is limited to 100 entries
1468 * and disk is unlimited, the group mappings will be different).
1469 *
1470 * @param key The cache key that we are ading to the groups.
1471 * @param newGroups the set of groups we want to add this cache entry to.
1472 * @param persist A flag to indicate whether the keys should be added to
1473 * the persistent cache layer.
1474 */
1475 private void addGroupMappings(