001: /*
002: * The contents of this file are subject to the
003: * Mozilla Public License Version 1.1 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
009: * See the License for the specific language governing rights and
010: * limitations under the License.
011: *
012: * The Initial Developer of the Original Code is Simulacra Media Ltd.
013: * Portions created by Simulacra Media Ltd are Copyright (C) Simulacra Media Ltd, 2004.
014: *
015: * All Rights Reserved.
016: *
017: * Contributor(s):
018: */
019: package org.openharmonise.commons.cache;
020:
021: import java.util.*;
022: import java.util.logging.*;
023:
024: import EDU.oswego.cs.dl.util.concurrent.*;
025:
026: /**
027: * Abstract class providing the base functionality for object caching. The
028: * cache has a maximum size limit and will page out as required if that limit
029: * is reached, paging out in descending order of time since last access. If the
030: * memory limit of the machine is reached before the cache pages our, the cache
031: * will clear and reduce all the cache sizes by a given ratio.
032: *
033: *
034: * @author Michael Bell
035: * @author John King
036: * @version $Revision: 1.2 $
037: *
038: */
039: public abstract class AbstractCache {
040:
041: /**
042: * Default cache size.
043: */
044: public static final int DEFAULT_CACHESIZE = 3500;
045:
046: /**
047: * Constant indicating a change based on a save action.
048: */
049: public static final String CHANGE_SAVE = "save";
050:
051: /**
052: * Constant indicating a change based on a delete action.
053: */
054: public static final String CHANGE_DELETE = "delete";
055:
056: /**
057: * Constant indicating a change based on an update action.
058: */
059: public static final String CHANGE_UPDATE = "update";
060:
061: /**
062: * Constant indicating a change based on an temporary action.
063: */
064: public static final String CHANGE_TEMP = "temp";
065:
066: /**
067: * Default percentage of the available memory that needs to be reached before
068: * the cache sizes are automatically reduced.
069: */
070: public static final double DEFAULT_CACHELIMIT_PERCENT = 0.9;
071:
072: /**
073: * Default percentage that cache sizes are reduced by when the memory limit
074: * has been reached.
075: */
076: public static final double DEFAULT_CACHEREDUCTION_RATIO = 0.95;
077:
078: /**
079: * List of caches which which retain their size if the memory limit is reached.
080: */
081: private static List m_protected_caches = null;
082:
083: /**
084: * Static Map containing all caches.
085: */
086: private static Map m_all_caches;
087:
088: /**
089: * Logger for this class.
090: */
091: private static Logger m_logger = Logger
092: .getLogger(AbstractCache.class.getName());
093:
094: static {
095: //initialise cache map
096: m_all_caches = Collections.synchronizedMap(new HashMap());
097: }
098:
099: /**
100: * Name of current cache.
101: */
102: private String m_cache_name;
103:
104: /**
105: * Map containing all cached objects.
106: */
107: private LRUCacheMap m_cache;
108:
109: /**
110: * Current cache size limit.
111: */
112: private int m_max_cache_size;
113:
114: /**
115: * Cache size limit at beginning of cache life.
116: */
117: private int m_init_max_cache_size;
118:
119: /**
120: * Estimate of avarage object size.
121: */
122: private long m_total_object_size_estimate = 0;
123:
124: /**
125: * Current heap size.
126: */
127: private int m_heap_size = -1; //
128:
129: /**
130: * Read-write lock to enable locking of cache access.
131: */
132: private ReadWriteLock lock = new WriterPreferenceReadWriteLock();
133:
134: /**
135: * Creates new cache with given parameters.
136: *
137: * @param cache_name the name of cache
138: * @param cache_size the initial size of cache
139: * @param cache_pagesize the paging size
140: * @throws CacheException if an error occurs in object construction
141: */
142: protected AbstractCache(String cache_name, int cache_size)
143: throws CacheException {
144: m_cache_name = cache_name;
145: m_init_max_cache_size = cache_size;
146: m_max_cache_size = cache_size;
147: m_cache = new LRUCacheMap(cache_size);
148:
149: m_all_caches.put(m_cache_name, this );
150: }
151:
152: /**
153: * Creates cache with default parameters and the given name.
154: *
155: * @param cache_name the name of cache
156: * @throws CacheException if an error occurs in object construction
157: */
158: protected AbstractCache(String cache_name) throws CacheException {
159: this (cache_name, DEFAULT_CACHESIZE);
160:
161: m_all_caches.put(m_cache_name, this );
162: }
163:
164: /**
165: * Returns cache object matching the given cache key.
166: *
167: * @param key the cache key which corresponds to required cached object
168: * @return the object in cache corresponding to given cache key
169: * @throws CacheException if an error occurs accessing object in cache
170: * @throws InterruptedException
171: * @throws RuntimeException if object is not in cache
172: */
173: public Object getObject(final Object key) throws CacheException {
174:
175: // obtain read lock
176: Object cached_object = null;
177: try {
178: lock.readLock().acquire();
179: cached_object = m_cache.get(key);
180: lock.readLock().release();
181: if (cached_object == null) {
182: cached_object = addToCache(key);
183: }
184:
185: } catch (InterruptedException e) {
186: throw new CacheException(e);
187: }
188:
189: return cached_object;
190: }
191:
192: /**
193: * Returns a pointer to the cached object referenced by the key.
194: *
195: * @param key the cache key which corresponds to required cached object
196: * @return a <code>CachePointer</code> referencing the cached object corresponding to given cache key
197: * @throws InterruptedException
198: * @throws CacheException
199: *
200: */
201: public CachePointer getObjectPointer(Object key)
202: throws CacheException {
203:
204: //Call 'getObject' to make sure any exceptions which may get
205: //thrown when getting the object are thrown here
206: Object obj = getObject(key);
207:
208: return new CachePointer(key, this );
209: }
210:
211: /**
212: * Adds an object to the cache which corresponds to the given cache key,
213: * calls <code>getCacheableObject</code> to get the object to cache.
214: *
215: * @param key the cache key which corresponds to required cached object
216: * @return the object which was existing in the cache for the given key,
217: * if there was already an object for the cache key.
218: *
219: * @throws RuntimeException if any exceptions are thrown during execution
220: * @throws CacheException note that the code will be changed to throw this
221: * exceptoin in the future rather than a <code>RuntimeException</code>
222: */
223: protected Object addToCache(Object key) throws CacheException {
224: Object cacheable_object = null;
225: try {
226:
227: cacheable_object = getCacheableObject(key);
228: addToCache(key, cacheable_object);
229: } catch (java.lang.OutOfMemoryError e) {
230: // note if we get here it seems to be too late to recover
231: // white pages appear
232: AbstractCache.clearAllCaches();
233:
234: Runtime runtime = Runtime.getRuntime();
235:
236: String error_msg = (m_cache_name
237: + " cache: "
238: + " Thrown OutOfMemoryError Exception"
239: + " Not enough memory to allocate cacheable object "
240: + ", free memory: " + runtime.freeMemory()
241: + ", total memory: " + runtime.totalMemory()
242: + ", probable needed memory: " + getAvgObjectSizeOfEstimate());
243:
244: m_logger.log(Level.SEVERE, error_msg, e);
245: } catch (Exception e) {
246: throw new CacheException(e);
247: }
248: return cacheable_object;
249: }
250:
251: /**
252: * Returns an estimate of the average size of an object in the cache.
253: *
254: * @return an estimate of the average size of an object in the cache
255: */
256: public long getAvgObjectSizeOfEstimate() {
257: // average, estimate only :)
258: return (m_cache.size() == 0) ? 0 : Math
259: .round(m_total_object_size_estimate / m_cache.size());
260: }
261:
262: /**
263: * Adds the <code>cacheable_object</code> to the cache with the given
264: * cache key <code>key</code>.
265: *
266: * @param key the cache key
267: * @param cacheable_object the object to be cached
268: * @throws InterruptedException
269: *
270: * @throws RuntimeException if any exceptions are thrown during execution
271: */
272: public void addToCache(Object key, Object cacheable_object) {
273:
274: if (m_logger.getLevel() == Level.FINE) {
275: StringBuffer sbuf = new StringBuffer();
276: sbuf.append(m_cache_name).append(": cache size - ").append(
277: m_cache.size()).append(", m_max_cache_size - ")
278: .append(m_max_cache_size);
279: m_logger.log(Level.FINE, sbuf.toString());
280: }
281:
282: try {
283: lock.writeLock().acquire();
284: m_cache.put(key, cacheable_object);
285: lock.writeLock().release();
286: } catch (InterruptedException e) {
287: throw new RuntimeException(e);
288: }
289:
290: }
291:
292: /**
293: * Removes object associated to the specified cache key from
294: * the cache.
295: *
296: * @param key the cache key
297: */
298: protected void removeObjectFromCache(Object key) {
299: try {
300: lock.writeLock().acquire();
301: m_cache.remove(key);
302: lock.writeLock().release();
303: } catch (InterruptedException e) {
304: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
305: }
306: }
307:
308: /**
309: * Clears all caches in system that extend this abstract cache class.
310: *
311: */
312: static synchronized public void clearAllCaches() {
313:
314: Iterator cache_iter = m_all_caches.values().iterator();
315:
316: while (cache_iter.hasNext()) {
317: ((AbstractCache) cache_iter.next()).clearCache();
318: }
319: }
320:
321: /**
322: * Clears this cache.
323: * @throws InterruptedException
324: *
325: */
326: public void clearCache() {
327: try {
328: lock.writeLock().acquire();
329:
330: m_cache.clear();
331: lock.writeLock().release();
332: } catch (InterruptedException e) {
333: throw new RuntimeException(e);
334: }
335: }
336:
337: /**
338: * Returns the current cache size, taken from the <code>Map</code> that
339: * contains all objects.
340: *
341: * @return the size of the cache
342: */
343: public int getCacheSize() {
344: return m_cache.size();
345: }
346:
347: /**
348: * Returns the name of the cache.
349: *
350: * @return the name of the cache
351: */
352: public String getCacheName() {
353: return this .m_cache_name;
354: }
355:
356: /**
357: * Returns the current maximum size of the cache.
358: *
359: * @return the current maximum size of the cache
360: */
361: public int getCacheMaxSize() {
362: return m_max_cache_size;
363: }
364:
365: /**
366: * Sets the maximum cache size.
367: *
368: * @param maxSize the maximum size for the cache
369: */
370: public void setCacheMaxSize(int maxSize) {
371:
372: try {
373: LRUCacheMap new_cache = new LRUCacheMap(maxSize);
374: lock.readLock().acquire();
375: int i = 0;
376: for (Iterator iter = m_cache.keySet().iterator(); iter
377: .hasNext()
378: && i < maxSize; i++) {
379: Object key = (Object) iter.next();
380: new_cache.put(key, m_cache.get(key));
381: }
382: lock.readLock().release();
383:
384: lock.writeLock().acquire();
385: m_cache = new_cache;
386: lock.writeLock().release();
387: } catch (InterruptedException e) {
388: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
389: }
390: }
391:
392: /**
393: * Returns the list of keys for the most accessed objects of the size given.
394: *
395: * @param num_favourites the number of keys to return in list
396: *
397: * @return a list of the keys of the most accessed objects
398: */
399: public List getCacheTopKeys(int num_favourites) {
400: List toReturn = null;
401:
402: try {
403: lock.readLock().acquire();
404: toReturn = new Vector(m_cache.values()).subList(0, (m_cache
405: .size() > num_favourites) ? num_favourites
406: : m_cache.size());
407: lock.readLock().release();
408: } catch (InterruptedException e) {
409: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
410: }
411: return toReturn;
412:
413: }
414:
415: /**
416: * Returns a <code>Set</code> of cache keys.
417: *
418: * @return a <code>Set</code> of cache keys
419: */
420: public Set getCacheKeys() {
421: Set toReturn = null;
422: try {
423: lock.readLock().acquire();
424: toReturn = new HashSet(m_cache.keySet());
425: lock.readLock().release();
426: } catch (InterruptedException e) {
427: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
428: }
429: return toReturn;
430: }
431:
432: /**
433: * Returns <code>true</code> if the given key is contained in the cache.
434: *
435: * @param cache_key a cache key
436: * @return <code>true</code> if this cache contains this key
437: * @throws InterruptedException
438: */
439: public boolean containsKey(Object cache_key)
440: throws InterruptedException {
441: lock.readLock().acquire();
442: boolean containsKeyFlag = m_cache.containsKey(cache_key);
443: lock.readLock().release();
444: return containsKeyFlag;
445: }
446:
447: /**
448: * Sets whether this cache checks objects for dependancies before
449: * paging the object out. If, when checking for dependancies, the object is
450: * found to have dependances the object will not be paged out.
451: *
452: *
453: * @param bIsDepAware <code>true</code> if the cache should check dependancies
454: * of an object before paging it out of the cache.
455: */
456: public void setDependancyAware(boolean bIsDepAware) {
457: m_cache.setDependancyAware(bIsDepAware);
458: }
459:
460: /**
461: * Returns <code>true</code> if this cache will check an object for
462: * dependancies before paging the object out.
463: *
464: * @return
465: */
466: public boolean isDependancyAware() {
467: return m_cache.isDependancyAware();
468: }
469:
470: /**
471: * Returns a cacheable object from the given key.
472: *
473: * @param key cache key for object
474: * @return Object to be cached
475: * @throws Exception if any error occurs
476: */
477: protected abstract Object getCacheableObject(Object key)
478: throws Exception;
479:
480: /**
481: * Changes object in cache, depending on the change code <code>sChangeCode</code>
482: * the object will be deleted from the cache or replaced with the new object
483: * <code>newObject</code>.
484: *
485: * @param cache_key key of changed object
486: * @param sChangeCode code of change
487: * @param newObject new object which corresponds with the cache key
488: * @throws InterruptedException
489: */
490: public void changeObject(Object cache_key, String sChangeCode,
491: Object newObject) {
492:
493: try {
494: lock.writeLock().acquire();
495: m_cache.remove(cache_key);
496:
497: if (sChangeCode.equalsIgnoreCase(CHANGE_SAVE)
498: || sChangeCode.equalsIgnoreCase(CHANGE_UPDATE)) {
499: addToCache(cache_key, newObject);
500: }
501: lock.writeLock().release();
502: } catch (InterruptedException e) {
503: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
504: throw new RuntimeException(e);
505: }
506: }
507:
508: }
|