KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > whirlycott > cache > CacheDecorator


1 /*
2  Copyright 2004 Philip Jacob <phil@whirlycott.com>
3  Seth Fitzsimmons <seth@note.amherst.edu>
4  
5  Licensed under the Apache License, Version 2.0 (the "License");
6  you may not use this file except in compliance with the License.
7  You may obtain a copy of the License at
8  
9  http://www.apache.org/licenses/LICENSE-2.0
10  
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16  */

17
18 package com.whirlycott.cache;
19
20 import java.util.Arrays JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.LinkedList JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Map.Entry;
26
27 import org.apache.commons.collections.CollectionUtils;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
32
33 import com.whirlycott.cache.policy.ExpirationTimePredicate;
34
35 /**
36  * Owns the cache tuning thread and provides housekeeping facilities for
37  * ManagedCache implementations. One CacheDecorator is created for each Cache
38  * named in the whirlycache.xml configuration file.
39  *
40  * @author Phil Jacob
41  */

42 public class CacheDecorator implements Runnable JavaDoc, Cache {
43
44     /** Logger */
45     private final static Log log = LogFactory.getLog(CacheDecorator.class);
46
47     /**
48      * This is the memory size for the adaptive caching in implementations that
49      * support it.
50      */

51     //TODO - make this a percentage of the max size or something...?
52
protected int adaptiveMemorySize = 5000;
53
54     /** Overflow buffer for the adaptive array. */
55     protected final int adaptiveMemorySizeOverflow = 512;
56
57     /** The counter for our current position in the adaptive result array. */
58     protected volatile int adaptiveResultCounter = 0;
59
60     /** The current time (accurate to sleepTime ms). */
61     protected volatile long currentTime = System.currentTimeMillis();
62
63     /** The array that holds the adaptive results. */
64     protected volatile int adaptiveResults[];
65
66     /** This is the cache we're managing. */
67     protected volatile ManagedCache managedCache;
68
69     /** The soft limit of the max number of items that can be put in the cache. */
70     protected int maxSize;
71
72     /** Name of this cache */
73     protected final String JavaDoc name;
74
75     /** The policy used during maintenance. */
76     private final CacheMaintenancePolicy policy;
77
78     /** We keep all of the stats in the recordkeeper. */
79     protected final RecordKeeper recordKeeper = new RecordKeeper();
80
81     /** There's a default sleep time of 10 seconds. */
82     protected long sleepTime = 10000L;
83
84     /**
85      * Reference to the cache tuning thread.
86      */

87     protected Thread JavaDoc tunerThread;
88
89     /**
90      * Constructor for a CacheDecorator.
91      * @param _managedCache
92      * @param _configuration
93      * @param policies
94      */

95     public CacheDecorator(final ManagedCache _managedCache,
96                           final CacheConfiguration _configuration,
97                           final CacheMaintenancePolicy[] policies) {
98
99         name = _configuration.getName();
100
101         if (null == name)
102             throw new IllegalArgumentException JavaDoc(Messages.getString("CacheDecorator.cache_config_cannot_be_null")); //$NON-NLS-1$
103

104         if (null == policies)
105             throw new IllegalArgumentException JavaDoc(Messages.getString("CacheDecorator.policies_cannot_be_null")); //$NON-NLS-1$
106

107         /*
108          * See:
109          * https://whirlycache.dev.java.net/issues/show_bug.cgi?id=7
110          * TODO - once we support multiple policies, this needs to be removed.
111          */

112         if (1 != policies.length)
113             throw new IllegalArgumentException JavaDoc(Messages.getString("CacheDecorator.must_provide_single_policy")); //$NON-NLS-1$
114

115         policy = policies[0];
116
117         //Adaptive size plus the buffer (to prevent ArrayIndexOutOfBoundsExceptions)s
118
adaptiveResults = new int[adaptiveMemorySize + adaptiveMemorySizeOverflow];
119
120         //This is the cache which we are managing.
121
managedCache = _managedCache;
122
123         //Do some configuration.
124
configure(_configuration);
125
126         //Start up the management thread.
127
tunerThread = new Thread JavaDoc(this);
128         final Object JavaDoc[] args = { name };
129         tunerThread.setName(Messages.getCompoundString("CacheDecorator.whirlycache_tuner", args)); //$NON-NLS-1$
130
tunerThread.setDaemon(true);
131         tunerThread.start();
132     }
133
134     /**
135      * Clears the cache.
136      */

137     public void clear() {
138         log.info(Messages.getString("CacheDecorator.clearing_cache")); //$NON-NLS-1$
139
managedCache.clear();
140         Arrays.fill(adaptiveResults, 0);
141     }
142
143     /**
144      * Configures the Cache being decorated.
145      */

146     protected void configure(final CacheConfiguration configuration) {
147         //Set up the max size.
148
setMaxSize(configuration.getMaxSize());
149
150         //Sleeptime.
151
setSleepTime(configuration.getTunerSleepTime() * 1000L);
152     }
153
154     /**
155      * Looks at the last 'n' queries to determine whether the Cache should turn
156      * on optimizations for a mostly-read environment (if the underlying
157      * implementation of ManagedCache supports this).
158      * @param _value varies depending on whether this was a read, write, or
159      * removal.
160      */

161     protected void doAdaptiveAccounting(final int _value) {
162         //We only care about the last 'n' adaptiveResults.
163
final int currentCounter = adaptiveResultCounter;
164         if (currentCounter >= adaptiveMemorySize) {
165             adaptiveResultCounter = 0;
166             adaptiveResults[0] = _value;
167         } else {
168             adaptiveResults[currentCounter] = _value;
169             adaptiveResultCounter++;
170         }
171     }
172
173     /**
174      * @return Returns the adaptiveMemorySize.
175      */

176     protected int getAdaptiveMemorySize() {
177         return adaptiveMemorySize;
178     }
179
180     /**
181      * Calculates the adaptive hit rate for this cache.
182      * @return adaptive hit ratio.
183      */

184     public float getAdaptiveRatio() {
185         final int copy[] = new int[adaptiveMemorySize];
186         System.arraycopy(adaptiveResults, 0, copy, 0, adaptiveMemorySize);
187         int positives = 0;
188         for (int i = 0; i < copy.length; i++) {
189             if (copy[i] == 1)
190                 positives++;
191         }
192         //log.info("Positives: " + positives + "; Total: " + adaptiveMemorySize);
193
return new Float JavaDoc(positives).floatValue() / new Float JavaDoc(adaptiveMemorySize).floatValue();
194     }
195
196     /**
197      * Returns an efficiency report string for this cache.
198      * @return efficiency report for this cache.
199      */

200     public String JavaDoc getEfficiencyReport() {
201         final Object JavaDoc[] args = {
202                 new Integer JavaDoc(managedCache.size()),
203                 new Long JavaDoc(recordKeeper.getTotalOperations()),
204                 new Long JavaDoc(recordKeeper.getHits()),
205                 new Float JavaDoc(getAdaptiveRatio()),
206                 new Float JavaDoc(getTotalHitrate())
207
208         };
209         return Messages.getCompoundString("CacheDecorator.efficiency_report", args); //$NON-NLS-1$
210
}
211
212     /**
213      * Get the maximum size of the cache.
214      * @return Returns the maxSize.
215      */

216     protected int getMaxSize() {
217         return maxSize;
218     }
219
220     /**
221      * @return Returns the policy in use for managing this cache.
222      */

223     protected CacheMaintenancePolicy getPolicy() {
224         return policy;
225     }
226
227     /**
228      * @return Returns the sleepTime.
229      */

230     protected long getSleepTime() {
231         return sleepTime;
232     }
233
234     /**
235      * Returns the total hitrate since this Cache was started.
236      * @return Total hitrate.
237      */

238     protected float getTotalHitrate() {
239         return new Long JavaDoc(recordKeeper.getHits()).floatValue() / new Long JavaDoc(recordKeeper.getTotalOperations()).floatValue();
240     }
241
242
243     /* (non-Javadoc)
244      * @see com.whirlycott.cache.Cache#remove(com.whirlycott.cache.Cacheable)
245      */

246     public Object JavaDoc remove(final Cacheable _key) {
247         final Object JavaDoc o = internalRemove(_key);
248         _key.onRemove(o);
249         return o;
250     }
251
252     /**
253      * Removes an Object from the Cache and returns the removed Object.
254      * @param _key key associated with object to remove.
255      */

256     public Object JavaDoc remove(final Object JavaDoc _key) {
257         return internalRemove(_key);
258     }
259
260     /**
261      * An internal remove operation.
262      * @param _key
263      * @return the object that was removed
264      */

265     protected Object JavaDoc internalRemove(final Object JavaDoc _key) {
266         if (Constants.BUILD_STATS_ENABLED)
267             recordKeeper.incrementTotalOperations();
268
269         if (_key != null) {
270             final Item cachedItem = (Item) managedCache.remove(_key);
271
272             //The compiler will optimize this.
273
if (Constants.BUILD_STATS_ENABLED)
274                 doAdaptiveAccounting(0);
275
276             return null == cachedItem ? null : cachedItem.getItem();
277         } else {
278             return null;
279         }
280     }
281
282
283     /* (non-Javadoc)
284      * @see com.whirlycott.cache.Cache#retrieve(com.whirlycott.cache.Cacheable)
285      */

286     public Object JavaDoc retrieve(final Cacheable _key) {
287         final Object JavaDoc o = internalRetrieve(_key);
288         _key.onRetrieve(o);
289         return o;
290     }
291
292     protected Object JavaDoc internalRetrieve(final Object JavaDoc _key) {
293         //Record a read.
294
if (Constants.BUILD_STATS_ENABLED) {
295             doAdaptiveAccounting(1);
296
297             //Increment the number of totalQuestions.
298
recordKeeper.incrementTotalOperations();
299         }
300
301         //Set up the return value
302
final Item cachedItem = (Item) managedCache.get(_key);
303         if (cachedItem != null) {
304
305             //Bump the numbers.
306
if (Constants.BUILD_STATS_ENABLED) {
307                 cachedItem.setUsed(recordKeeper.getTotalOperations());
308                 cachedItem.incrementCount();
309             }
310
311             //Increment the adaptive algorithm and the hitcounter.
312
final Object JavaDoc retval = cachedItem.getItem();
313             if (retval != null && Constants.BUILD_STATS_ENABLED)
314                 recordKeeper.incrementHits();
315
316             return retval;
317         } else {
318
319             //Found nothing inside the cache.
320
return null;
321         }
322     }
323
324     /**
325      * Gets an Object from the Cache.
326      */

327     public Object JavaDoc retrieve(final Object JavaDoc _key) {
328         return internalRetrieve(_key);
329     }
330
331     /**
332      * Starts up the background maintenance thread.
333      */

334     public void run() {
335         log.debug(Messages.getString("CacheDecorator.tuning_thread_started")); //$NON-NLS-1$
336

337         final Thread JavaDoc t = Thread.currentThread();
338
339         try {
340             while (tunerThread == t) {
341                 // updates the cache's notion of the "current time"
342
if (Constants.ITEM_EXPIRATION_ENABLED)
343                     currentTime = System.currentTimeMillis();
344
345                 if (Constants.BUILD_STATS_ENABLED) {
346                     recordKeeper.startTuneCycle();
347
348                     //Adapt to mostly read or mostly write.
349
tuneCache();
350                 }
351
352                 // expire Items in need of expiration
353
// TODO move this to its own policy
354
if (Constants.ITEM_EXPIRATION_ENABLED)
355                     expireItems();
356
357                 //Perform tuning and maintenance.
358
policy.performMaintenance();
359
360                 try {
361                     //Sleep for a bit
362
Thread.sleep(sleepTime);
363                 } catch (final InterruptedException JavaDoc e) {
364                     log.info(Messages.getString("CacheDecorator.tuning_thread_interrupted")); //$NON-NLS-1$
365
}
366
367                 if (Constants.BUILD_STATS_ENABLED) {
368
369                     if (sleepTime > 0L) {
370                         recordKeeper.calculateQueriesPerSecond(sleepTime);
371                     }
372
373                     logStatistics();
374                 }
375
376                 if (log.isDebugEnabled())
377                     log.debug(Messages.getString("CacheDecorator.cache_tuning_complete")); //$NON-NLS-1$
378
}
379         } catch (final RuntimeException JavaDoc e) {
380             log.fatal(Messages.getString("CacheDecorator.unexpected_shutdown"), e); //$NON-NLS-1$
381
}
382
383         log.debug(Messages.getString("CacheDecorator.shutting_down")); //$NON-NLS-1$
384
}
385
386     /**
387      * Log some cache usage data depending on some conditions.
388      *
389      */

390     protected void logStatistics() {
391         if (sleepTime > 0L && log.isDebugEnabled()) {
392             final Object JavaDoc[] args = { new Long JavaDoc(recordKeeper.getQueriesPerSecond()) };
393             log.debug(Messages.getCompoundString("CacheDecorator.query_rate", args)); //$NON-NLS-1$
394
}
395
396         //Print the efficiency report
397
if (log.isInfoEnabled())
398             log.info(getEfficiencyReport());
399     }
400
401     /**
402      * @param adaptiveMemorySize The adaptiveMemorySize to set.
403      */

404     protected void setAdaptiveMemorySize(final int adaptiveMemorySize) {
405         this.adaptiveMemorySize = adaptiveMemorySize;
406     }
407
408     /**
409      * Specify the ManagedCache that this Decorator owns.
410      * @param cache The ManagedCache to set.
411      */

412     protected void setManagedCache(final ManagedCache cache) {
413         this.managedCache = cache;
414         log.debug(Messages.getString("CacheDecorator.managing_cache_with_type") + cache.getClass()); //$NON-NLS-1$
415
}
416
417     /**
418      * @param maxSize The maxSize to set.
419      */

420     protected void setMaxSize(final int maxSize) {
421         this.maxSize = maxSize;
422     }
423
424     /**
425      * Tells the underlying ManagedCache implementation to turn on
426      * optimizations for a mostly-read environment (if supported).
427      * @param _mostlyRead whether most operations are expected to be reads.
428      */

429     public void setMostlyRead(final boolean _mostlyRead) {
430         if (Constants.BUILD_STATS_ENABLED)
431             recordKeeper.incrementTotalOperations();
432
433         managedCache.setMostlyRead(_mostlyRead);
434     }
435
436     /**
437      * @param sleepTime The sleepTime to set.
438      */

439     protected void setSleepTime(final long sleepTime) {
440         this.sleepTime = sleepTime;
441     }
442
443     /** Shut down this cache. */
444     public void shutdown() {
445         if (log.isDebugEnabled())
446             log.debug(Messages.getString("CacheDecorator.shutting_down_cache") + name); //$NON-NLS-1$
447
if (Constants.BUILD_STATS_ENABLED) {
448             log.info(getEfficiencyReport());
449             recordKeeper.reset();
450         }
451
452         final Thread JavaDoc tunerThreadToKill = tunerThread;
453         tunerThread = null;
454         tunerThreadToKill.interrupt();
455     }
456
457     /**
458      * Returns the number of items in the Cache.
459      * @return number of items in the cache.
460      */

461     public int size() {
462         if (Constants.BUILD_STATS_ENABLED)
463             recordKeeper.incrementTotalOperations();
464
465         return managedCache.size();
466     }
467
468
469     /* (non-Javadoc)
470      * @see com.whirlycott.cache.Cache#store(com.whirlycott.cache.Cacheable, java.lang.Object)
471      */

472     public void store(final Cacheable key, final Object JavaDoc value) {
473         internalStore(key, value, -1L);
474         key.onStore(value);
475     }
476
477
478     /* (non-Javadoc)
479      * @see com.whirlycott.cache.Cache#store(com.whirlycott.cache.Cacheable, java.lang.Object, long)
480      */

481     public void store(final Cacheable _key, final Object JavaDoc _value, final long _expiresAfter) {
482         internalStore(_key, _value, _expiresAfter);
483         _key.onStore(_value);
484     }
485
486     /**
487      * Store an object in the cache.
488      */

489     public void store(final Object JavaDoc _key, final Object JavaDoc _value) {
490         internalStore(_key, _value, -1L);
491     }
492
493
494     /* (non-Javadoc)
495      * @see com.whirlycott.cache.Cache#store(java.lang.Object, java.lang.Object, long)
496      */

497     public void store(final Object JavaDoc _key, final Object JavaDoc _value, final long _expiresAfter) {
498         internalStore(_key, _value, _expiresAfter);
499     }
500
501     /**
502      * All stores go through this.
503      * @param _key
504      * @param _value
505      * @param _expiresAfter specified in milliseconds
506      */

507     protected void internalStore(final Object JavaDoc _key, final Object JavaDoc _value, final long _expiresAfter) {
508         if (Constants.BUILD_STATS_ENABLED)
509             recordKeeper.incrementTotalOperations();
510
511         if (_key != null && _value != null) {
512             final Item cachedValue = new Item(_value, currentTime, _expiresAfter);
513             managedCache.put(_key, cachedValue);
514
515             if (Constants.BUILD_STATS_ENABLED)
516                 doAdaptiveAccounting(0);
517         }
518     }
519
520     /**
521      * Tunes the managed cache for a mostly read or write environment.
522      */

523     protected void tuneCache() {
524         final float adaptiveRatio = getAdaptiveRatio();
525         if (adaptiveRatio > 0.5F) {
526             log.debug(Messages.getString("CacheDecorator.read_optimizations_on") + adaptiveRatio); //$NON-NLS-1$
527
managedCache.setMostlyRead(true);
528         } else {
529             log.debug(Messages.getString("CacheDecorator.read_optimizations_off") + adaptiveRatio); //$NON-NLS-1$
530
managedCache.setMostlyRead(false);
531         }
532     }
533
534     /**
535      * Expires Items in need of expiration.
536      */

537     protected void expireItems() {
538         //Sort the entries in the cache.
539
final List JavaDoc entries = new LinkedList JavaDoc(new ConcurrentHashMap(managedCache).entrySet());
540         CollectionUtils.filter(entries, new ExpirationTimePredicate(currentTime));
541         final Object JavaDoc[] args = { new Integer JavaDoc(entries.size()) };
542         log.debug(Messages.getCompoundString("CacheDecorator.expiration_count", args)); //$NON-NLS-1$
543
for (final Iterator JavaDoc i = entries.iterator(); i.hasNext();) {
544             final Map.Entry JavaDoc entry = (Entry) i.next();
545             if (entry != null) {
546                 //log.trace("Removing: " + entry.getKey());
547
managedCache.remove(entry.getKey());
548             }
549         }
550     }
551
552 }
Popular Tags