KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > vipan > util > ExpiringCache


1 package com.vipan.util;
2
3 import java.util.*;
4 import org.apache.log4j.*;
5 import org.apache.commons.collections.map.LRUMap;
6 import org.apache.commons.collections.MapIterator;
7
8
9 /**
10 This class is <b>not</b> meant to be thread-safe. It does not appear to make
11 sense. It is easy to do with Collections.synchronizedMap(cacheMap) and not
12 using the iterator (instead, copy keys to a different array and remove in
13 timerTask using that array). Making operations synchronized will slow down
14 the operations.
15
16 WARNING: Not yet tested in production. Use at your own risk.
17 */

18 public class ExpiringCache {
19
20     static Category logger = Category.getInstance(ExpiringCache.class);
21
22   public static final long DEFAULT_TIME_TO_LIVE = 10 * 60 * 1000;
23   public static final long DEFAULT_ACCESS_TIMEOUT = 5 * 60 * 1000;
24   public static final long DEFAULT_TIMER_INTERVAL = 2 * 60 * 1000;
25
26   long ttl = DEFAULT_TIME_TO_LIVE;
27   long ato = DEFAULT_ACCESS_TIMEOUT;
28   long tiv = DEFAULT_TIMER_INTERVAL;
29
30   LRUMap cacheMap;
31   Timer cacheManager;
32
33
34   protected void finalize() throws Throwable JavaDoc {
35     if (cacheManager != null) cacheManager.cancel();
36   }
37
38   public ExpiringCache() {
39     cacheMap = new LRUMap();
40     initialize();
41   }
42
43   // All times in millisecs
44
public ExpiringCache(long timeToLive, long accessTimeout,
45     int maximumCachedQuantity, long timerInterval
46   ) {
47     ttl = timeToLive;
48     ato = accessTimeout;
49     cacheMap = new LRUMap(maximumCachedQuantity);
50     tiv = timerInterval;
51     initialize();
52   }
53
54
55   public void setTimeToLive(long milliSecs) {
56     ttl = milliSecs;
57     initialize();
58   }
59
60
61
62   public void setAccessTimeout(long milliSecs) {
63     ato = milliSecs;
64     initialize();
65   }
66
67
68
69   public void setCleaningInterval(long milliSecs) {
70     tiv = milliSecs;
71     initialize();
72   }
73
74
75   public void initialize() {
76     if (logger.isDebugEnabled()) logger.debug("initialize() started");
77     if (cacheManager != null) cacheManager.cancel();
78     cacheManager = new Timer(true);
79     cacheManager.schedule(
80       new TimerTask() {
81         public void run() {
82           NDC.push("TimerTask");
83           long now = System.currentTimeMillis();
84           try {
85             MapIterator itr = cacheMap.mapIterator();
86             while (itr.hasNext()) {
87               Object JavaDoc key = itr.next();
88               CachedObject cobj = (CachedObject) itr.getValue();
89               if (cobj == null || cobj.hasExpired(now)) {
90                 if (logger.isDebugEnabled()) logger.debug(
91                   "Removing " + key + ": Idle time=" +
92                   (now - cobj.timeAccessedLast) + "; Stale time:" +
93                   (now - cobj.timeCached));
94                 itr.remove();
95                 Thread.yield();
96               }
97             }
98           }
99           catch (ConcurrentModificationException cme) {
100             /*
101             Ignorable. This is just a timer cleaning up.
102             It will catchup on cleaning next time it runs.
103             */

104             if (logger.isDebugEnabled()) logger.debug(
105               "Ignorable ConcurrentModificationException");
106           }
107           NDC.remove();
108         }
109       },
110       0,
111       tiv
112     );
113   }
114
115
116
117
118   public int howManyObjects() {
119     return cacheMap.size();
120   }
121
122
123   public void clear() {
124     cacheMap.clear();
125   }
126
127     /**
128   If the given key already maps to an existing object and the new object
129   is not equal to the existing object, existing object is overwritten
130   and the existing object is returned; otherwise null is returned.
131   You may want to check the return value for null-ness to make sure you
132   are not overwriting a previously cached object. May be you can use a
133   different key for your object if you do not intend to overwrite.
134   */

135   public Object JavaDoc admit(Object JavaDoc key, Object JavaDoc dataToCache) {
136     //cacheMap.put(key, new CachedObject(dataToCache));
137
//return null;
138

139     CachedObject cobj = (CachedObject) cacheMap.get(key);
140     if (cobj == null) {
141       cacheMap.put(key, new CachedObject(dataToCache));
142       return null;
143     }
144     else {
145       Object JavaDoc obj = cobj.getCachedData(key);
146       if (obj == null) {
147         if (dataToCache == null) {
148           // Avoids creating unnecessary new cachedObject
149
// Number of accesses is not reset because object is the same
150
cobj.timeCached = cobj.timeAccessedLast = System.currentTimeMillis();
151           return null;
152         }
153         else {
154           cacheMap.put(key, new CachedObject(dataToCache));
155           return null;
156         }
157       }
158       else if (obj.equals(dataToCache)) {
159         // Avoids creating unnecessary new cachedObject
160
// Number of accesses is not reset because object is the same
161
cobj.timeCached = cobj.timeAccessedLast = System.currentTimeMillis();
162         return null;
163       }
164       else {
165         cacheMap.put(key, new CachedObject(dataToCache));
166         return obj;
167       }
168     }
169   }
170
171
172
173   public Object JavaDoc admit(Object JavaDoc key, Object JavaDoc dataToCache, long objectTimeToLive, long objectIdleTimeout) {
174     //cacheMap.put(key, new CachedObject(dataToCache));
175
//return null;
176

177     CachedObject cobj = (CachedObject) cacheMap.get(key);
178     if (cobj == null) {
179       cacheMap.put(key, new CachedObject(dataToCache, objectTimeToLive, objectIdleTimeout));
180       return null;
181     }
182     else {
183       Object JavaDoc obj = cobj.getCachedData(key);
184       if (obj == null) {
185         if (dataToCache == null) {
186           // Avoids creating unnecessary new cachedObject
187
// Number of accesses is not reset because object is the same
188
cobj.timeCached = cobj.timeAccessedLast = System.currentTimeMillis();
189           cobj.objectTTL = objectTimeToLive;
190           cobj.objectIdleTimeout = objectIdleTimeout;
191           cobj.userTimeouts = true;
192           return null;
193         }
194         else {
195           cacheMap.put(key, new CachedObject(dataToCache, objectTimeToLive, objectIdleTimeout));
196           return null;
197         }
198       }
199       else if (obj.equals(dataToCache)) {
200         // Avoids creating unnecessary new cachedObject
201
// Number of accesses is not reset because object is the same
202
cobj.timeCached = cobj.timeAccessedLast = System.currentTimeMillis();
203         cobj.objectTTL = objectTimeToLive;
204         cobj.objectIdleTimeout = objectIdleTimeout;
205         cobj.userTimeouts = true;
206         return null;
207       }
208       else {
209         cacheMap.put(key, new CachedObject(dataToCache, objectTimeToLive, objectIdleTimeout));
210         return obj;
211       }
212     }
213   }
214
215
216
217   public Object JavaDoc recover(Object JavaDoc key) {
218     CachedObject cobj = (CachedObject) cacheMap.get(key);
219     if (cobj == null) return null;
220     else return cobj.getCachedData(key);
221   }
222
223
224   public void discard(Object JavaDoc key) {
225     cacheMap.remove(key);
226   }
227
228
229   public long whenCached(Object JavaDoc key) {
230     CachedObject cobj = (CachedObject) cacheMap.get(key);
231     if (cobj == null) return 0;
232     return cobj.timeCached;
233   }
234
235
236   public long whenLastAccessed(Object JavaDoc key) {
237     CachedObject cobj = (CachedObject) cacheMap.get(key);
238     if (cobj == null) return 0;
239     return cobj.timeAccessedLast;
240   }
241
242
243   public int howManyTimesAccessed(Object JavaDoc key) {
244     CachedObject cobj = (CachedObject) cacheMap.get(key);
245     if (cobj == null) return 0;
246     return cobj.numberOfAccesses;
247   }
248
249
250   /**
251   A cached object, needed to store attributes such as the last time
252   it was accessed.
253   */

254   protected class CachedObject {
255     Object JavaDoc cachedData;
256     long timeCached;
257     long timeAccessedLast;
258     int numberOfAccesses;
259     long objectTTL;
260     long objectIdleTimeout;
261     boolean userTimeouts;
262
263
264     CachedObject(Object JavaDoc cachedData) {
265       long now = System.currentTimeMillis();
266       this.cachedData = cachedData;
267       timeCached = now;
268       timeAccessedLast = now;
269       ++numberOfAccesses;
270     }
271
272     CachedObject(Object JavaDoc cachedData, long timeToLive, long idleTimeout) {
273       long now = System.currentTimeMillis();
274       this.cachedData = cachedData;
275       objectTTL = timeToLive;
276       objectIdleTimeout = idleTimeout;
277       userTimeouts = true;
278       timeCached = now;
279       timeAccessedLast = now;
280       ++numberOfAccesses;
281     }
282
283
284     Object JavaDoc getCachedData(Object JavaDoc key) {
285       long now = System.currentTimeMillis();
286       if (hasExpired(now)) {
287                 cachedData = null;
288                 cacheMap.remove(key);
289                 return null;
290             }
291       timeAccessedLast = now;
292       ++numberOfAccesses;
293       return cachedData;
294     }
295
296     boolean hasExpired(long now) {
297       long usedTTL = userTimeouts?objectTTL:ttl;
298       long usedATO = userTimeouts?objectIdleTimeout:ato;
299
300       if (now > timeAccessedLast + usedATO ||
301         now > timeCached + usedTTL
302       ) {
303         return true;
304       }
305       else return false;
306     }
307
308
309   }
310
311
312
313 } // END OF CLASS
314

315
316
Popular Tags