001: /*
002: * hgcommons 7
003: * Hammurapi Group Common Library
004: * Copyright (C) 2003 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.cache;
024:
025: import java.lang.ref.ReferenceQueue;
026: import java.lang.ref.SoftReference;
027: import java.util.HashMap;
028: import java.util.HashSet;
029: import java.util.Iterator;
030: import java.util.LinkedList;
031: import java.util.Map;
032: import java.util.Set;
033: import java.util.Timer;
034: import java.util.TimerTask;
035: import java.util.WeakHashMap;
036:
037: import biz.hammurapi.config.Component;
038: import biz.hammurapi.config.ConfigurationException;
039: import biz.hammurapi.metrics.MeasurementCategory;
040: import biz.hammurapi.util.Acceptor;
041:
042: /**
043: * Memory sensitive cache. Uses soft references to cache objects.
044: * @author Pavel Vlasov
045: * @version $Revision: 1.7 $
046: */
047: public class MemoryCache extends AbstractProducer implements Cache,
048: Component {
049: protected Map cache = new HashMap();
050: protected Map reverseCache = new WeakHashMap();
051:
052: private ReferenceQueue queue = new ReferenceQueue();
053: protected Producer producer;
054: private Cache fallBack;
055: private MeasurementCategory measurementCategory;
056:
057: private void addMeasurement(String name, double value, long time) {
058: if (measurementCategory != null) {
059: measurementCategory.addMeasurement(name, value, time);
060: }
061: }
062:
063: private void addMeasurement(String name, double value) {
064: if (measurementCategory != null) {
065: measurementCategory.addMeasurement(name, value, 0);
066: }
067: }
068:
069: private class CacheReference extends SoftReference {
070:
071: private Object key;
072: private long expires;
073: private long time;
074:
075: /**
076: * @param referent
077: * @param q
078: * @param expires
079: */
080: public CacheReference(Object key, Object value, long time,
081: long expires) {
082: super (value, queue);
083: this .key = key;
084: this .expires = expires;
085: this .time = time;
086: }
087:
088: /**
089: * @param string
090: * @param i
091: * @param l
092: */
093: public void addMeasurement(String name, double value) {
094: MemoryCache.this .addMeasurement(name, value);
095: }
096: }
097:
098: /**
099: * Used as entry if fallBack cache is used.
100: * @author Pavel Vlasov
101: * @version $Revision: 1.7 $
102: */
103: private class FallBackEntry {
104: Object value;
105: private long expires;
106: private long time;
107: private Object key;
108:
109: FallBackEntry(Object key, Object value, long time, long expires) {
110: this .key = key;
111: this .value = value;
112: this .expires = expires;
113: this .time = time;
114: }
115:
116: protected void finalize() throws Throwable {
117: if (fallBack.isActive()
118: && (expires <= 0 || System.currentTimeMillis() < expires)) {
119: fallBack.put(key, value, time, expires);
120: }
121:
122: super .finalize();
123: }
124: }
125:
126: private static class ReferenceThread extends Thread {
127: private ReferenceQueue queue;
128: private Map cache;
129:
130: private ReferenceThread(ReferenceQueue queue, Map cache) {
131: this .queue = queue;
132: this .cache = cache;
133: setDaemon(true);
134: setName("Removes cleared entries");
135: start();
136: }
137:
138: public void run() {
139: try {
140: while (true) {
141: CacheReference cacheReference = (CacheReference) queue
142: .remove();
143: Object key = cacheReference.key;
144: synchronized (cache) {
145: cache.remove(key);
146: }
147: cacheReference.addMeasurement("garbage collected",
148: 1);
149: }
150: } catch (InterruptedException e) {
151: return;
152: }
153: }
154: }
155:
156: private Thread referenceThread = new ReferenceThread(queue, cache);
157:
158: private static class JanitorTask extends TimerTask {
159: private Map cache;
160: private MeasurementCategory measurementCategory;
161: private Map reverseCache;
162:
163: private void addMeasurement(String name, double value, long time) {
164: if (measurementCategory != null) {
165: measurementCategory.addMeasurement(name, value, time);
166: }
167: }
168:
169: private JanitorTask(Map cache, Map reverseCache,
170: MeasurementCategory measurementCategory) {
171: this .cache = cache;
172: this .reverseCache = reverseCache;
173: this .measurementCategory = measurementCategory;
174: }
175:
176: public void run() {
177: long now = System.currentTimeMillis();
178: int expired = 0;
179: synchronized (cache) {
180: Iterator it = new LinkedList(cache.keySet()).iterator();
181: while (it.hasNext()) {
182: Object key = it.next();
183: CacheReference cacheReference = (CacheReference) cache
184: .get(key);
185: long expires = (cacheReference).expires;
186: if (expires > 0 && expires < now) {
187: cache.remove(key);
188: reverseCache.remove(cacheReference.get());
189: expired++;
190: }
191: }
192: addMeasurement("size", cache.size(), now);
193: }
194:
195: if (expired > 0) {
196: addMeasurement("expired", expired, now);
197: }
198: }
199: }
200:
201: private TimerTask janitorTask;
202:
203: /**
204: * Default cache cleanup interval.
205: */
206: public static final long CLEANUP_INTERVAL = 60000;
207:
208: /**
209: * Constructs cache with default cleanup interval (1 minute).
210: * @param producer Producer
211: * @param fallBack Fallback cache
212: * @param measurementCategory Measurement category to report cache statistics
213: */
214: public MemoryCache(Producer producer, Cache fallBack,
215: MeasurementCategory measurementCategory) {
216: this (producer, fallBack, measurementCategory, null,
217: CLEANUP_INTERVAL);
218: }
219:
220: /**
221: * Constructs cache with default cleanup interval (1 minute).
222: * @param producer Producer
223: * @param fallBack Fallback cache
224: * @param measurementCategory Measurement category to report cache statistics
225: * @param cleanupInterval Interval between removals of expired entries.
226: * @param timer Timer which will invoke cleanup tasks, if it is null then a new timer is created internally.
227: * Use this parameter to share timers between multiple caches in order to reduce number of threads in the
228: * application.
229: */
230: public MemoryCache(Producer producer, Cache fallBack,
231: MeasurementCategory measurementCategory, Timer timer,
232: long cleanupInterval) {
233: super ();
234:
235: this .producer = producer;
236: if (producer != null) {
237: producer.addCache(this );
238: }
239:
240: this .fallBack = fallBack;
241: if (fallBack != null) {
242: addCache(fallBack);
243: }
244:
245: this .measurementCategory = measurementCategory;
246: this .timer = timer;
247:
248: this .cleanupInterval = cleanupInterval;
249: }
250:
251: private boolean isOwnTimer;
252: private Timer timer;
253: private long cleanupInterval;
254:
255: public void put(Object key, Object value, long time,
256: long expirationTime) {
257: checkShutdown();
258: synchronized (cache) {
259: cache.put(key, new CacheReference(key, toValue(key, value,
260: time, expirationTime), time, expirationTime));
261: reverseCache.put(value, key);
262: addMeasurement("put", 1, time);
263: }
264: }
265:
266: /**
267: * @param key
268: * @param value
269: * @param expirationTime
270: * @return
271: */
272: private Object toValue(Object key, Object value, long time,
273: long expirationTime) {
274: return fallBack == null ? value : new FallBackEntry(key, value,
275: time, expirationTime);
276: }
277:
278: private Object fromValue(Object object) {
279: if (object instanceof FallBackEntry) {
280: return ((FallBackEntry) object).value;
281: }
282: return object;
283: }
284:
285: public Entry get(Object key) {
286: checkShutdown();
287: synchronized (cache) {
288: CacheReference cr = (CacheReference) cache.get(key);
289: long now = System.currentTimeMillis();
290: addMeasurement("get", 1, now);
291: boolean doFallBack = true;
292: if (cr != null) {
293: if ((cr.expires > 0 && cr.expires < now)
294: || cr.get() == null) {
295: cache.remove(key);
296: reverseCache.remove(fromValue(cr.get()));
297: doFallBack = false;
298: } else {
299: final Object o = fromValue(cr.get());
300: final long et = cr.expires;
301: final long time = cr.time;
302: return new Entry() {
303:
304: public long getExpirationTime() {
305: return et;
306: }
307:
308: public long getTime() {
309: return time;
310: }
311:
312: public Object get() {
313: return o;
314: }
315:
316: };
317: }
318: }
319:
320: if (doFallBack && fallBack != null) {
321: Entry entry = fallBack.get(key);
322: if (entry != null) {
323: cache.put(key, new CacheReference(key, toValue(key,
324: entry.get(), entry.getTime(), entry
325: .getExpirationTime()), entry
326: .getTime(), entry.getExpirationTime()));
327: reverseCache.put(entry.get(), key);
328: return entry;
329: }
330: }
331:
332: if (producer != null) {
333: Entry entry = producer.get(key);
334: cache.put(key, new CacheReference(key, toValue(key,
335: entry.get(), entry.getTime(), entry
336: .getExpirationTime()), entry.getTime(),
337: entry.getExpirationTime()));
338: reverseCache.put(entry.get(), key);
339: addMeasurement("produce", 1, now);
340: return entry;
341: }
342:
343: return null;
344: }
345: }
346:
347: public void clear() {
348: synchronized (cache) {
349: cache.clear();
350: reverseCache.clear();
351:
352: if (fallBack != null) {
353: fallBack.clear();
354: }
355: }
356: }
357:
358: public void remove(Object key) {
359: synchronized (cache) {
360: CacheReference cr = (CacheReference) cache.remove(key);
361: if (cr != null) {
362: reverseCache.remove(fromValue(cr.get()));
363: }
364: }
365: addMeasurement("remove", 1);
366: onRemove(key);
367: }
368:
369: public void remove(Acceptor acceptor) {
370: synchronized (cache) {
371: Iterator it = cache.keySet().iterator();
372: int removed = 0;
373: while (it.hasNext()) {
374: Object key = it.next();
375: if (acceptor.accept(key)) {
376: Entry entry = (Entry) cache.get(key);
377: if (entry != null) {
378: reverseCache.remove(fromValue(entry.get()));
379: }
380: it.remove();
381: removed++;
382: onRemove(key);
383: }
384: }
385: if (removed > 0) {
386: addMeasurement("remove", removed);
387: }
388: }
389: }
390:
391: private boolean shutDown = false;
392:
393: private void checkShutdown() {
394: if (shutDown) {
395: throw new IllegalStateException("Shut down");
396: }
397: }
398:
399: public void stop() {
400: if (!shutDown) {
401: cache.clear();
402: reverseCache.clear();
403: referenceThread.interrupt();
404: if (janitorTask != null) {
405: janitorTask.cancel();
406: }
407: if (isOwnTimer && timer != null) {
408: timer.cancel();
409: }
410: shutDown = true;
411: }
412: }
413:
414: protected void finalize() throws Throwable {
415: if (isActive()) {
416: stop();
417: }
418: super .finalize();
419: }
420:
421: public Set keySet() {
422: HashSet ret = new HashSet();
423: synchronized (cache) {
424: ret.addAll(cache.keySet());
425: }
426:
427: if (producer != null) {
428: Set pkeys = producer.keySet();
429: if (pkeys != null) {
430: ret.addAll(pkeys);
431: }
432: }
433:
434: if (fallBack != null) {
435: Set fkeys = fallBack.keySet();
436: if (fkeys != null) {
437: ret.addAll(fkeys);
438: }
439: }
440:
441: return ret;
442: }
443:
444: public boolean isActive() {
445: return !shutDown;
446: }
447:
448: /**
449: * Creates timer if neccessary, creates cleanup task and schedules it.
450: */
451: public void start() throws ConfigurationException {
452: if (timer == null) {
453: timer = new Timer(true);
454: isOwnTimer = true;
455: }
456:
457: janitorTask = new JanitorTask(cache, reverseCache,
458: measurementCategory);
459: timer.schedule(janitorTask, cleanupInterval, cleanupInterval);
460: }
461:
462: public void setOwner(Object owner) {
463: // TODO Nothing
464: }
465: }
|