KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jahia > services > cache > simplecache > SimpleCache


1 /*
2  * ____.
3  * __/\ ______| |__/\. _______
4  * __ .____| | \ | +----+ \
5  * _______| /--| | | - \ _ | : - \_________
6  * \\______: :---| : : | : | \________>
7  * |__\---\_____________:______: :____|____:_____\
8  * /_____|
9  *
10  * . . . i n j a h i a w e t r u s t . . .
11  *
12  *
13  *
14  * ----- BEGIN LICENSE BLOCK -----
15  * Version: JCSL 1.0
16  *
17  * The contents of this file are subject to the Jahia Community Source License
18  * 1.0 or later (the "License"); you may not use this file except in
19  * compliance with the License. You may obtain a copy of the License at
20  * http://www.jahia.org/license
21  *
22  * Software distributed under the License is distributed on an "AS IS" basis,
23  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
24  * for the rights, obligations and limitations governing use of the contents
25  * of the file. The Original and Upgraded Code is the Jahia CMS and Portal
26  * Server. The developer of the Original and Upgraded Code is JAHIA Ltd. JAHIA
27  * Ltd. owns the copyrights in the portions it created. All Rights Reserved.
28  *
29  * The Developer of the Shared Modifications is Jahia Solution Sarl.
30  * Portions created by the Initial Developer are Copyright (C) 2002 by the
31  * Initial Developer. All Rights Reserved.
32  *
33  * Contributor(s):
34  * 29-JUL-2003, Jahia Solutions Sarl, Fulco Houkes : Initial version
35  *
36  * ----- END LICENSE BLOCK -----
37  */

38
39
40 package org.jahia.services.cache.simplecache;
41
42 import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
43 import java.util.*;
44
45 import org.jahia.services.cache.Cache;
46 import org.jahia.services.cache.CacheEntry;
47 import org.jahia.services.cache.CacheFactory;
48 import org.jahia.services.cache.CacheListener;
49 import org.jahia.services.cache.JMSHub;
50
51
52 /** <p>This is the root class for all the cache in Jahia.</p>
53  *
54  * <p>This cache can handle synchronization messages with other Jahia server instances
55  * by using JMS messages. This synchronization is automatically initialized and used
56  * when the JMS synchronization is activated in the Jahia configuration file.</p>
57  *
58  * <p>Each cache <b>must</b> has a distinct <code>name</code>, and the
59  * associated <code>description</code> is only for debugging purpose or for
60  * monitoring display. Each cache uses a MRU (Most Recent Used) list to determine which
61  * elements will be removed from the cache when the cache limit has been reached. In this
62  * case, the least used cache entry will be removed.</p>
63  *
64  * <p>Each object inserted in the cache will be wrapped into a
65  * {@link org.jahia.services.cache.CacheEntry CacheEntry} instance, which contains
66  * among other information, the entry's expiration date and last accessed date.</p>
67  *
68  * <p>Using the {@link org.jahia.services.cache.simplecache.SimpleCache#getCacheEntry getCacheEntry}
69  * method will retrieve the cache entry instance and <b>not</b> the object stored into
70  * the entry instance. To access the stored object instance, the
71  * {@link org.jahia.services.cache.simplecache.SimpleCache#get get} method should be used instead or use
72  * the getter methods of the {@link org.jahia.services.cache.CacheEntry CacheEntry}
73  * class.</p>
74  *
75  * <p>Caches can only be retrieved and created through the
76  * {@link org.jahia.services.cache.CacheFactory CacheFactory} class, which is responsible
77  * for managing all the caches.</p>
78  *
79  * @author Fulco Houkes, Copyright (c) 2003 by Jahia Ltd.
80  * @version 1.0
81  * @since Jahia 4.0
82  * @see org.jahia.services.cache.CacheFactory CacheFactory
83  * @see org.jahia.services.cache.CacheEntry CacheEntry
84  * @see org.jahia.services.cache.CacheListener CacheListener
85  */

86 public class SimpleCache implements Cache {
87
88     /** logging. */
89     final private static org.apache.log4j.Logger logger =
90             org.apache.log4j.Logger.getLogger (SimpleCache.class);
91
92     /** cache map instance. */
93     private Map cache;
94
95     /** Cache name. */
96     private String JavaDoc name;
97
98     /** number of cache hit that could successfully be served. */
99     private long successHitCount = 0;
100
101     /** total number of cache hits. */
102     private long totalHitCount = 0;
103
104     /** defines the cache size limit. -1 defines no limit */
105     private int cacheLimit = -1;
106
107
108     /** reference to the JMS hub to send messages. */
109     private JMSHub jmsHub = null;
110
111     private ArrayList listeners = null;
112
113
114     /** <p>Creates a new <code>Cache</code> instance.</p>
115      *
116      * @param aName the cache name
117      * @param hub the JMS cache hub, <code>null</code> defined a local cache
118      * behaviour, without synchronization.
119      */

120     public SimpleCache (final String JavaDoc aName, final JMSHub hub) {
121         init (aName, hub);
122     }
123
124
125     /** <p>Retrieve the cache entry associated to the <code>entryKey</code> argument or
126      * <code>null</code> when the requested cache entry is not available, or when the
127      * <code>entryKey</code> argument is <code>null</code>.</p>
128      *
129      * <p>The last accessed date of the returned cache entry will be updated to the current
130      * date (& time) and it's set as the most recent used entry in the cache's MRU list.
131      * Expired cache entries will automatically be removed from the cache and
132      * <code>null</code> will be returned as result.</p>
133      *
134      * @param entryKey the cache entry key. <code>null</code> keys will
135      * return <code>null</code>.
136      *
137      * @return the reference to the cache entry
138      */

139     public CacheEntry getCacheEntry (Object JavaDoc entryKey) {
140
141         // don't know what to do with a null key, just return null in this case!
142
if (entryKey == null)
143             return null;
144
145         // check the cache is still valid
146
if (cache == null) {
147             // use huge size for increase first load perfs
148
cache = new ConcurrentReaderHashMap (2003);
149         }
150
151         // a new cache hit
152
totalHitCount++;
153
154         // Try to get the cache entry out of the JCS cache
155
CacheEntry entry = (CacheEntry)cache.get (entryKey);
156         if (entry == null) {
157             if (logger.isDebugEnabled()) {
158                 // log the result
159
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc("Entry [");
160                 buffer.append(entryKey.toString());
161                 buffer.append("] could not be found in cache [");
162                 buffer.append(name);
163                 buffer.append("]!");
164                 logger.debug(buffer.toString());
165             }
166             return null;
167         }
168
169         // get the entry expiration date
170
Date date = entry.getExpirationDate ();
171
172         // check if the entry is expired
173
if (date != null) {
174             if (date.getTime() <= System.currentTimeMillis() ) {
175                 // entry has expired, we must remove it and then exit.
176
logger.debug ("Cache entry has expired, ignoring entry and removing...");
177                 remove (entryKey);
178                 return null;
179
180             } else {
181                 logger.debug ("Cache entry has not expired, continuing...");
182             }
183         }
184
185         // at this point, the cache entry could be found -> another successful cache hit!
186
successHitCount++;
187
188         // increase the entry hits
189
entry.incrementHits ();
190
191         // update the last accessed time for the JCS cache entry.
192
entry.setLastAccessedTimeNow ();
193
194         return entry;
195     }
196
197     /** <p>Fetchs the cache entry associated to the <code>entryKey</code> and returns the
198      * object stored in fetched cache entry. <code>null</code> is returned when the
199      * <code>entryKey</code> is <code>null</code>, or when no cache entry could be found
200      * for the specified <code>entryKey</code>.</p>
201      *
202      * @param entryKey the key associated to the requested object. A <code>null</code>
203      * key will return in a <code>null</code> result.
204      *
205      * @return the object associated to the code <code>entryKey</code> cache entry.
206      */

207     public Object JavaDoc get (Object JavaDoc entryKey) {
208         if (entryKey == null) {
209             logger.debug ("Cannot fetch with an null entry key!!!");
210             return null;
211         }
212
213         CacheEntry entry = getCacheEntry (entryKey);
214         if (entry != null) {
215             return entry.getObject ();
216         }
217         return null;
218     }
219
220
221     /** <p>Add a new object into the cache. This method encapsulates automatically the specified
222      * <code>entryObj</code> object into a new
223      * {@link org.jahia.services.cache.CacheEntry CacheEntry} instance and associated the
224      * new cache entry to the <code>entryKey</code> key. If there is already an entry
225      * associated with the <code>entryKey</code> key in the cache, that entry will be removed
226      * and replaced with the new specified entry.</p>
227      *
228      * @param entryKey the object's associated key, <code>null</code> is not allowed
229      * @param entryObj the reference to the object to be cached.
230      */

231     public void put (Object JavaDoc entryKey, Object JavaDoc entryObj) {
232         put(entryKey, entryObj, true);
233     }
234
235     /** <p>Add a new object into the cache. This method encapsulates automatically the specified
236      * <code>entryObj</code> object into a new
237      * {@link org.jahia.services.cache.CacheEntry CacheEntry} instance and associated the
238      * new cache entry to the <code>entryKey</code> key. If there is already an entry
239      * associated with the <code>entryKey</code> key in the cache, that entry will be removed
240      * and replaced with the new specified entry.</p>
241      *
242      * @param entryKey the object's associated key, <code>null</code> is not allowed
243      * @param entryObj the reference to the object to be cached.
244      * @param propagate specifies whether the cache update should be sent
245      * across the cluster. If you don't know what this means, set it to true.
246      * Set it to false it you want to update the cache ONLY locally, for example
247      * when implementing a cache listener method.
248      */

249     public void put (Object JavaDoc entryKey, Object JavaDoc entryObj, boolean propagate) {
250         if (entryKey == null) {
251             logger.debug ("Cannot add an object with an empty key!!");
252             return;
253         }
254
255         CacheEntry entry = new CacheEntry (entryObj);
256         putCacheEntry (entryKey, entry, propagate);
257     }
258
259
260     /** <p>Add a new entry into the cache.</p>
261      *
262      * <p>The process will be ignored when either <code>entryKey</code> and/or
263      * <code>entry</code> is/are <code>null</code>. If there is already an entry
264      * associated with the <code>entryKey</code> key in the cache, that entry will be removed
265      * and replaced with the new specified entry.</p>
266      *
267      * @param entryKey the cache entry key, <code>null</code> is not allowed.
268      * @param entry the reference to the entry to be cached, <code>null</code> is not
269      * allowed.
270      * @param propagate specifies whether the cache update should be sent
271      * across the cluster. If you don't know what this means, set it to true.
272      * Set it to false it you want to update the cache ONLY locally, for example
273      * when implementing a cache listener method.
274      */

275     public synchronized void putCacheEntry (Object JavaDoc entryKey, CacheEntry entry, boolean propagate) {
276
277         internalPut(entryKey, entry);
278         
279         if ((listeners != null) && (listeners.size() > 0)) {
280             for (int i = 0; i < listeners.size(); i++) {
281                 CacheListener listener = (CacheListener) listeners.get(i);
282                 if (listener != null)
283                     listener.onCachePut(name, entryKey);
284             }
285         }
286         // send the remove message if needed
287
if ((jmsHub != null) && (propagate) ) {
288             if (!jmsHub.sendPutMessage (this, entryKey, entry))
289                 logger.debug ("Could not send the update message!");
290         }
291     }
292
293     private boolean internalPut (Object JavaDoc entryKey, CacheEntry entry) {
294         if ((entryKey == null) || (entry == null)) {
295             logger.debug ("null cache entry key or entry object, cannot cache such an object!");
296             return false;
297         }
298
299         // check the cache is still valid
300
if (cache == null) {
301                 cache = new ConcurrentReaderHashMap(2003);
302         }
303
304         if (getCacheLimit () == 0) {
305             logger.debug ("cache is deactivated. Aborting store.");
306             return false;
307         }
308
309
310         // Add the entry in the cache
311
if (cache.containsKey (entryKey)) {
312             if (logger.isDebugEnabled()) {
313                 StringBuffer JavaDoc buffer = new StringBuffer JavaDoc("Updating key [");
314                 buffer.append(entryKey.toString());
315                 buffer.append("] into cache [");
316                 buffer.append(name);
317                 buffer.append(", cache size before set=");
318                 buffer.append(size());
319                 logger.debug(buffer.toString());
320             }
321         } else {
322             // let's handle maximum size stuff here
323
checkCacheSize ();
324
325             if (logger.isDebugEnabled()) {
326                 StringBuffer JavaDoc buffer = new StringBuffer JavaDoc("Adding key [");
327                 buffer.append(entryKey.toString());
328                 buffer.append("] into cache [");
329                 buffer.append(name);
330                 buffer.append(", cache size before set=");
331                 buffer.append(size());
332                 logger.debug(buffer.toString());
333             }
334         }
335         cache.put (entryKey, entry);
336         return true;
337     }
338
339
340     /** <p>Initialize the cache.</p>
341      *
342      * @param aName the cache name
343      * @param hub the JMS cache hub, <code>null</code> defined a local cache
344      * behaviour, without synchronization.
345      */

346     private void init (final String JavaDoc aName, JMSHub hub) {
347         // set the cache name
348
this.name = aName;
349
350         // set the JMS hub connection
351
jmsHub = hub;
352
353         // create the cache HashMap
354
cache = new ConcurrentReaderHashMap(2003);
355     }
356
357
358     /** <p>Removes the cache entry associate to the key <code>entryKey</code>. The removal
359      * operation is canceled in case the <code>entryKey</code> is <code>null</code>; does
360      * nothing when the <code>entryKey</code> is unknown in the cache.</p>
361      *
362      * @param entryKey the cache entry key, <code>null</code> is not allowed
363      */

364     public synchronized void remove (Object JavaDoc entryKey) {
365         internalRemove (entryKey);
366
367         // Create the removal message if needed
368
if (jmsHub != null) {
369             jmsHub.sendRemoveMessage (this, entryKey);
370         }
371     }
372
373     /**
374      * <p>
375      * Return true if cache is empty.
376      * </p>
377      *
378      * @return true if cache is empty
379      */

380     final public boolean isEmpty() {
381         return cache.isEmpty();
382     }
383
384     /** <p>Return the current number of entries in the cache.</p>
385      *
386      * @return the current cache size
387      */

388     final public int size () {
389         return cache.size ();
390     }
391
392
393     /** <p>Returns the maximum size allowed for the cache.</p>
394      *
395      * @return an integer representing the maximum cache size. Returns -1 if
396      * there is no limit set.
397      */

398     final public int getCacheLimit () {
399         return cacheLimit;
400     }
401
402
403     /** <p>Set the cache size limit. -1 will define an unlimited cache size.</p>
404      *
405      * @param limit the new size limit
406      */

407     final public void setCacheLimit (int limit) {
408         cacheLimit = limit;
409     }
410
411
412     /** <p>Retrieves the cache name.</p>
413      *
414      * @return the cache region name
415      */

416     final public String JavaDoc getName () {
417         return name;
418     }
419
420
421     /** <p>Flushs all the cache entries. By sending the flushing event to all the
422      * cache's listeners.</p>
423      */

424     public final void flush () {
425         flush (true);
426     }
427
428     /** <p>Flushs all the cache entries. When <code>sendToListeners</code> is
429      * <code>true</code>, the event is send to the cache's listeners.</p>
430      */

431     public void flush(boolean propagate) {
432
433         synchronized (this) {
434             // clears the cache
435
cache.clear();
436
437             // reset the cache statistics
438
successHitCount = 0;
439             totalHitCount = 0;
440         }
441
442         logger.debug("Flushed all entries from cache [" + name + "]");
443
444         // when requested, propagate the flush event to the cache listeners and the JMS
445
if (propagate) {
446             // Create the flushing message if needed
447
if (jmsHub != null) {
448                 if (!jmsHub.sendFlushMessage(this))
449                     logger.debug("Could not send the flush message!");
450             }
451
452             // call the listeners if present
453
if ((listeners != null) && (listeners.size() > 0)) {
454                 for (int i = 0; i < listeners.size(); i++) {
455                     CacheListener listener = (CacheListener) listeners.get(i);
456                     if (listener != null)
457                         listener.onCacheFlush(name);
458                 }
459             }
460
461         } else {
462             logger.debug("Got cache flush request without event propagation");
463         }
464
465
466     }
467
468     /** <p>Retrieves an array holding all the keys contained by the cache.</p>
469      *
470      * <p>Note: <i>this method can be time consumming and blocks the cache the time
471      * necessary for reading all the keys. The returned keys will not necessarily reflet
472      * the internal state of the cache, as a remote cache put or remove can change the cache's
473      * state as soon as this method returns.</i></p>
474      *
475      * @return an array holding all the current keys of all the cache entries.
476      */

477     public synchronized Object JavaDoc[] keys () {
478         if (cache != null) {
479             Set keys = cache.keySet ();
480             return (Object JavaDoc[])(keys.toArray (new Object JavaDoc[0])).clone();
481         }
482         return new Object JavaDoc[0];
483     }
484
485
486     /** <p>Retrieves the number of cache hits that could successfully be served.</p>
487      *
488      * @return the number of cache hits that could successfully be served
489      */

490     final public long getSuccessHits () {
491         return successHitCount;
492     }
493
494
495     /** <p>Retrieves the total number of cache hits.</p>
496      *
497      * @return the number of cache hits
498      */

499     final public long getTotalHits () {
500         return totalHitCount;
501     }
502
503
504     /** <p>Retrieves the percentage of cache hit that could successfully be served.
505      * This efficiency is useful for tuning the maximum cache size. Cache size should
506      * be changed increased for low efficiency percentages.</p>
507      *
508      * @return the percentage of cache hit that could successfully be served
509      */

510     public double getCacheEfficiency () {
511         return (successHitCount * 100.0) / totalHitCount;
512     }
513
514
515     /** <p>This method is called by the JMS Message Consumer when a <i>remove</i> event
516      * message was received for this cache instance.</p>
517      *
518      * <p>This event signals the cache, the entry, associated to the key
519      * <code>entryKey</code>, has been removed from the cache in another clustered
520      * Jahia instance. To keep caches synchronized, the cache entry must also to be
521      * removed from this cache.</p>
522      *
523      * @param entryKey the entry key associated with the JMS messages
524      */

525     public void onRemove (Object JavaDoc entryKey) {
526         internalRemove (entryKey);
527     }
528
529
530     /** <p>This method is called by the JMS Message Consumer when a <i>put</i> event
531      * message was received for this cache instance</p>
532      *
533      * <p>This event signals the cache, a new cache entry (or the existing cache entry
534      * was updated) associated to the entry key <code>entryKey</code> was added into
535      * the cache from another clustered Jahia instance. To keep caches synchronized,
536      * the cache entry must also to be removed/updated/put into this cache.</p>
537      *
538      * <p>Currently, the existing cache entry associated to the <code>entryKey</code> is
539      * removed from the cache and no other action is taken.</p>
540      * <p>Note: <i>the best action would have been
541      * to fetch the associated/updated objet out of the persistence layer and insert it into
542      * the cache. But this has not been implemented yet.</i></p>
543      *
544      * @param entryKey the entry key associated with the JMS messages
545      */

546     public void onPut (Object JavaDoc entryKey, Object JavaDoc entryValue) {
547         // internalRemove (entryKey);
548
internalPut(entryKey, (CacheEntry) entryValue);
549         // call the listeners if present
550
if ((listeners != null) && (listeners.size() > 0)) {
551             for (int i = 0; i < listeners.size(); i++) {
552                 CacheListener listener = (CacheListener) listeners.get(i);
553                 if (listener != null)
554                     listener.onCachePut(name, entryKey);
555             }
556         }
557     }
558
559
560     /** <p>This method is called by the JMS Message Consumer when a <i>flush</i> event
561      * message was received for this cache instance</p>
562      *
563      * <p>This event signals this cache another clustered cache instance
564      * (of the same name as this cache) has flushed all its entries. To keep the caches
565      * synchronized, this cache must also flush all its entries.<p>
566      */

567     public void onFlush () {
568         flush (false);
569     }
570
571
572     /** Checks if the specified <code>entryKey</code> is present in the cache.
573      *
574      * @param entryKey the entry key to be checked.
575      *
576      * @return <code>true</code> when the entry key is present in the cache,
577      * otherwise return <code>false</code>
578      */

579     final public boolean containsKey (final Object JavaDoc entryKey) {
580         return cache.containsKey (entryKey);
581     }
582
583
584     /** Check the cache has not reached its limit. If this turns to be the case,
585      * the last elements will be removed until the cache size is lower than its limit.
586      */

587     private void checkCacheSize () {
588         if (getCacheLimit () <= 0) {
589             return;
590         }
591
592         while (cache.size () >= getCacheLimit ()) {
593             logger.debug ("Cache is at limit(" + getCacheLimit () + "), removing least used entry.");
594
595             // we base the search for the oldest entry by using it's last
596
// access date time.
597
Iterator entryIter = cache.entrySet().iterator();
598             long minTime = Long.MAX_VALUE;
599             Object JavaDoc minEntryKey = null;
600             while (entryIter.hasNext()) {
601                 Map.Entry curEntry = (Map.Entry) entryIter.next();
602                 CacheEntry curCacheEntry = (CacheEntry) curEntry.getValue();
603                 if (curCacheEntry.getLastAccessedTimeMillis() < minTime) {
604                     minTime = curCacheEntry.getLastAccessedTimeMillis();
605                     minEntryKey = curEntry.getKey();
606                 }
607             }
608             remove(minEntryKey);
609         }
610     }
611
612
613     /** <p>Remove the cache entry associated to the <code>entryKey</code>. This method
614      * <b>does not</b> take any synchronization actions.</p>
615      *
616      * @param entryKey the cache entry's key to be removed
617      */

618     private synchronized void internalRemove (Object JavaDoc entryKey) {
619         if (entryKey == null)
620             return;
621
622         // remove the object from the cache
623
cache.remove (entryKey);
624         logger.debug ("Removed the entry [" + entryKey.toString () +
625                       "] from cache [" + name + "]!");
626     }
627
628     /**
629      * <p>Register a new cache listener.</p>
630      * <p>When the specified <code>listener</code> is <code>null</code> or already present
631      * within the listeners list, the registration process is ignored.</p>
632      *
633      * @param listener the reference of the cache listener to register.
634      * @since Jahia 4.0.2
635      */

636     public synchronized void registerListener (CacheListener listener) {
637         if (listener == null)
638             return;
639
640         if (listeners == null) {
641             listeners = new ArrayList();
642
643         } else if (listeners.contains (listener)) {
644             return;
645         }
646
647         listeners.add (listener);
648     }
649
650     /**
651      * <p>Unregister a cache listener.</p>
652      * <p>When there is not cache listener registered, or the specified <code>listener</code>
653      * is <code>null</code>, the unregistration process is ignored.</p>
654      *
655      * @param listener the reference of the cache listener to register
656      * @since Jahia 4.0.2
657      */

658     public synchronized void unregisterListener (CacheListener listener) {
659         if ((listeners == null) || (listener == null))
660             return;
661
662         listeners.remove (listener);
663     }
664 }
665
Popular Tags