KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > cache > TransactionalCache


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.cache;
18
19 import java.io.IOException JavaDoc;
20 import java.io.Serializable JavaDoc;
21 import java.util.List JavaDoc;
22
23 import net.sf.ehcache.Cache;
24 import net.sf.ehcache.CacheException;
25 import net.sf.ehcache.CacheManager;
26 import net.sf.ehcache.Element;
27
28 import org.alfresco.error.AlfrescoRuntimeException;
29 import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
30 import org.alfresco.repo.transaction.TransactionListener;
31 import org.alfresco.util.EqualsHelper;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.springframework.beans.factory.InitializingBean;
35 import org.springframework.util.Assert;
36
37 /**
38  * A 2-level cache that mainains both a transaction-local cache and
39  * wraps a non-transactional (shared) cache.
40  * <p>
41  * It uses the <b>Ehcache</b> <tt>Cache</tt> for it's per-transaction
42  * caches as these provide automatic size limitations, etc.
43  * <p>
44  * Instances of this class <b>do not require a transaction</b>. They will work
45  * directly with the shared cache when no transaction is present. There is
46  * virtually no overhead when running out-of-transaction.
47  * <p>
48  * 3 caches are maintained.
49  * <ul>
50  * <li>Shared backing cache that should only be accessed by instances of this class</li>
51  * <li>Lazily created cache of updates made during the transaction</li>
52  * <li>Lazily created cache of deletions made during the transaction</li>
53  * </ul>
54  * <p>
55  * When the cache is {@link #clear() cleared}, a flag is set on the transaction.
56  * The shared cache, instead of being cleared itself, is just ignored for the remainder
57  * of the tranasaction. At the end of the transaction, if the flag is set, the
58  * shared transaction is cleared <i>before</i> updates are added back to it.
59  * <p>
60  * Because there is a limited amount of space available to the in-transaction caches,
61  * when either of these becomes full, the cleared flag is set. This ensures that
62  * the shared cache will not have stale data in the event of the transaction-local
63  * caches dropping items.
64  *
65  * @author Derek Hulley
66  */

67 public class TransactionalCache<K extends Serializable JavaDoc, V extends Serializable JavaDoc>
68         implements SimpleCache<K, V>, TransactionListener, InitializingBean
69 {
70     private static final String JavaDoc RESOURCE_KEY_TXN_DATA = "TransactionalCache.TxnData";
71     private static final String JavaDoc VALUE_DELETE = "TransactionalCache.DeleteMarker";
72     
73     private static Log logger = LogFactory.getLog(TransactionalCache.class);
74
75     /** a name used to uniquely identify the transactional caches */
76     private String JavaDoc name;
77     
78     /** the shared cache that will get updated after commits */
79     private SimpleCache<Serializable JavaDoc, Serializable JavaDoc> sharedCache;
80
81     /** the manager to control Ehcache caches */
82     private CacheManager cacheManager;
83     
84     /** the maximum number of elements to be contained in the cache */
85     private int maxCacheSize = 500;
86     
87     /** a unique string identifying this instance when binding resources */
88     private String JavaDoc resourceKeyTxnData;
89
90     /**
91      * @see #setName(String)
92      */

93     public String JavaDoc toString()
94     {
95         return name;
96     }
97     
98     public boolean equals(Object JavaDoc obj)
99     {
100         if (obj == this)
101         {
102             return true;
103         }
104         if (obj == null)
105         {
106             return false;
107         }
108         if (!(obj instanceof TransactionalCache))
109         {
110             return false;
111         }
112         TransactionalCache that = (TransactionalCache) obj;
113         return EqualsHelper.nullSafeEquals(this.name, that.name);
114     }
115     
116     public int hashCode()
117     {
118         return name.hashCode();
119     }
120     
121     /**
122      * Set the shared cache to use during transaction synchronization or when no transaction
123      * is present.
124      *
125      * @param sharedCache
126      */

127     public void setSharedCache(SimpleCache<Serializable JavaDoc, Serializable JavaDoc> sharedCache)
128     {
129         this.sharedCache = sharedCache;
130     }
131
132     /**
133      * Set the manager to activate and control the cache instances
134      *
135      * @param cacheManager
136      */

137     public void setCacheManager(CacheManager cacheManager)
138     {
139         this.cacheManager = cacheManager;
140     }
141
142     /**
143      * Set the maximum number of elements to store in the update and remove caches.
144      * The maximum number of elements stored in the transaction will be twice the
145      * value given.
146      * <p>
147      * The removed list will overflow to disk in order to ensure that deletions are
148      * not lost.
149      *
150      * @param maxCacheSize
151      */

152     public void setMaxCacheSize(int maxCacheSize)
153     {
154         this.maxCacheSize = maxCacheSize;
155     }
156
157     /**
158      * Set the name that identifies this cache from other instances. This is optional.
159      *
160      * @param name
161      */

162     public void setName(String JavaDoc name)
163     {
164         this.name = name;
165     }
166
167     /**
168      * Ensures that all properties have been set
169      */

170     public void afterPropertiesSet() throws Exception JavaDoc
171     {
172         Assert.notNull(name, "name property not set");
173         Assert.notNull(cacheManager, "cacheManager property not set");
174         // generate the resource binding key
175
resourceKeyTxnData = RESOURCE_KEY_TXN_DATA + "." + name;
176     }
177
178     /**
179      * To be used in a transaction only.
180      */

181     private TransactionData getTransactionData()
182     {
183         TransactionData data = (TransactionData) AlfrescoTransactionSupport.getResource(resourceKeyTxnData);
184         if (data == null)
185         {
186             String JavaDoc txnId = AlfrescoTransactionSupport.getTransactionId();
187             data = new TransactionData();
188             // create and initialize caches
189
data.updatedItemsCache = new Cache(
190                     name + "_"+ txnId + "_updates",
191                     maxCacheSize, false, true, 0, 0);
192             data.removedItemsCache = new Cache(
193                     name + "_" + txnId + "_removes",
194                     maxCacheSize, false, true, 0, 0);
195             try
196             {
197                 cacheManager.addCache(data.updatedItemsCache);
198                 cacheManager.addCache(data.removedItemsCache);
199             }
200             catch (CacheException e)
201             {
202                 throw new AlfrescoRuntimeException("Failed to add txn caches to manager", e);
203             }
204             finally
205             {
206                 // ensure that we get the transaction callbacks as we have bound the unique
207
// transactional caches to a common manager
208
AlfrescoTransactionSupport.bindListener(this);
209             }
210             AlfrescoTransactionSupport.bindResource(resourceKeyTxnData, data);
211         }
212         return data;
213     }
214     
215     /**
216      * Checks the transactional removed and updated caches before checking the shared cache.
217      */

218     public boolean contains(K key)
219     {
220         Object JavaDoc value = get(key);
221         if (value == null)
222         {
223             return false;
224         }
225         else
226         {
227             return true;
228         }
229     }
230
231     /**
232      * Checks the per-transaction caches for the object before going to the shared cache.
233      * If the thread is not in a transaction, then the shared cache is accessed directly.
234      */

235     @SuppressWarnings JavaDoc("unchecked")
236     public V get(K key)
237     {
238         boolean ignoreSharedCache = false;
239         // are we in a transaction?
240
if (AlfrescoTransactionSupport.getTransactionId() != null)
241         {
242             TransactionData txnData = getTransactionData();
243             try
244             {
245                 if (!txnData.isClearOn) // deletions cache is still reliable
246
{
247                     // check to see if the key is present in the transaction's removed items
248
if (txnData.removedItemsCache.get(key) != null)
249                     {
250                         // it has been removed in this transaction
251
if (logger.isDebugEnabled())
252                         {
253                             logger.debug("get returning null - item has been removed from transactional cache: \n" +
254                                     " cache: " + this + "\n" +
255                                     " key: " + key);
256                         }
257                         return null;
258                     }
259                 }
260                 
261                 // check for the item in the transaction's new/updated items
262
Element element = txnData.updatedItemsCache.get(key);
263                 if (element != null)
264                 {
265                     // element was found in transaction-specific updates/additions
266
if (logger.isDebugEnabled())
267                     {
268                         logger.debug("Found item in transactional cache: \n" +
269                                 " cache: " + this + "\n" +
270                                 " key: " + key + "\n" +
271                                 " value: " + element.getValue());
272                     }
273                     return (V) element.getValue();
274                 }
275             }
276             catch (CacheException e)
277             {
278                 throw new AlfrescoRuntimeException("Cache failure", e);
279             }
280             // check if the cleared flag has been set - cleared flag means ignore shared as unreliable
281
ignoreSharedCache = txnData.isClearOn;
282         }
283         // no value found - must we ignore the shared cache?
284
if (!ignoreSharedCache)
285         {
286             // go to the shared cache
287
if (logger.isDebugEnabled())
288             {
289                 logger.debug("No value found in transaction - fetching instance from shared cache: \n" +
290                         " cache: " + this + "\n" +
291                         " key: " + key + "\n" +
292                         " value: " + sharedCache.get(key));
293             }
294             return (V) sharedCache.get(key);
295         }
296         else // ignore shared cache
297
{
298             if (logger.isDebugEnabled())
299             {
300                 logger.debug("No value found in transaction and ignoring shared cache: \n" +
301                         " cache: " + this + "\n" +
302                         " key: " + key);
303             }
304             return null;
305         }
306     }
307
308     /**
309      * Goes direct to the shared cache in the absence of a transaction.
310      * <p>
311      * Where a transaction is present, a cache of updated items is lazily added to the
312      * thread and the <tt>Object</tt> put onto that.
313      */

314     public void put(K key, V value)
315     {
316         // are we in a transaction?
317
if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction
318
{
319             // no transaction
320
sharedCache.put(key, value);
321             // done
322
if (logger.isDebugEnabled())
323             {
324                 logger.debug("No transaction - adding item direct to shared cache: \n" +
325                         " cache: " + this + "\n" +
326                         " key: " + key + "\n" +
327                         " value: " + value);
328             }
329         }
330         else // transaction present
331
{
332             TransactionData txnData = getTransactionData();
333             // we have a transaction - add the item into the updated cache for this transaction
334
// are we in an overflow condition?
335
if (txnData.updatedItemsCache.getMemoryStoreSize() >= maxCacheSize)
336             {
337                 // overflow about to occur or has occured - we can only guarantee non-stale
338
// data by clearing the shared cache after the transaction. Also, the
339
// shared cache needs to be ignored for the rest of the transaction.
340
txnData.isClearOn = true;
341             }
342             Element element = new Element(key, value);
343             txnData.updatedItemsCache.put(element);
344             // remove the item from the removed cache, if present
345
txnData.removedItemsCache.remove(key);
346             // done
347
if (logger.isDebugEnabled())
348             {
349                 logger.debug("In transaction - adding item direct to transactional update cache: \n" +
350                         " cache: " + this + "\n" +
351                         " key: " + key + "\n" +
352                         " value: " + value);
353             }
354         }
355     }
356
357     /**
358      * Goes direct to the shared cache in the absence of a transaction.
359      * <p>
360      * Where a transaction is present, a cache of removed items is lazily added to the
361      * thread and the <tt>Object</tt> put onto that.
362      */

363     public void remove(K key)
364     {
365         // are we in a transaction?
366
if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction
367
{
368             // no transaction
369
sharedCache.remove(key);
370             // done
371
if (logger.isDebugEnabled())
372             {
373                 logger.debug("No transaction - removing item from shared cache: \n" +
374                         " cache: " + this + "\n" +
375                         " key: " + key);
376             }
377         }
378         else // transaction present
379
{
380             TransactionData txnData = getTransactionData();
381             // is the shared cache going to be cleared?
382
if (txnData.isClearOn)
383             {
384                 // don't store removals
385
}
386             else
387             {
388                 // are we in an overflow condition?
389
if (txnData.removedItemsCache.getMemoryStoreSize() >= maxCacheSize)
390                 {
391                     // overflow about to occur or has occured - we can only guarantee non-stale
392
// data by clearing the shared cache after the transaction. Also, the
393
// shared cache needs to be ignored for the rest of the transaction.
394
txnData.isClearOn = true;
395                     if (logger.isDebugEnabled())
396                     {
397                         logger.debug("In transaction - removal cache reach capacity reached: \n" +
398                                 " cache: " + this + "\n" +
399                                 " txn: " + AlfrescoTransactionSupport.getTransactionId());
400                     }
401                 }
402                 else
403                 {
404                     // add it from the removed cache for this txn
405
Element element = new Element(key, VALUE_DELETE);
406                     txnData.removedItemsCache.put(element);
407                 }
408             }
409             // remove the item from the udpated cache, if present
410
txnData.updatedItemsCache.remove(key);
411             // done
412
if (logger.isDebugEnabled())
413             {
414                 logger.debug("In transaction - adding item direct to transactional removed cache: \n" +
415                         " cache: " + this + "\n" +
416                         " key: " + key);
417             }
418         }
419     }
420
421     /**
422      * Clears out all the caches.
423      */

424     public void clear()
425     {
426         // clear local caches
427
if (AlfrescoTransactionSupport.getTransactionId() != null)
428         {
429             if (logger.isDebugEnabled())
430             {
431                 logger.debug("In transaction clearing cache: \n" +
432                         " cache: " + this + "\n" +
433                         " txn: " + AlfrescoTransactionSupport.getTransactionId());
434             }
435             
436             TransactionData txnData = getTransactionData();
437             // the shared cache must be cleared at the end of the transaction
438
// and also serves to ensure that the shared cache will be ignored
439
// for the remainder of the transaction
440
txnData.isClearOn = true;
441             try
442             {
443                 txnData.updatedItemsCache.removeAll();
444                 txnData.removedItemsCache.removeAll();
445             }
446             catch (IOException JavaDoc e)
447             {
448                 throw new AlfrescoRuntimeException("Failed to clear caches", e);
449             }
450         }
451         else // no transaction
452
{
453             if (logger.isDebugEnabled())
454             {
455                 logger.debug("No transaction - clearing shared cache");
456             }
457             // clear shared cache
458
sharedCache.clear();
459         }
460     }
461
462     /**
463      * NO-OP
464      */

465     public void flush()
466     {
467     }
468
469     public void beforeCommit(boolean readOnly)
470     {
471     }
472
473     public void beforeCompletion()
474     {
475     }
476
477     /**
478      * Merge the transactional caches into the shared cache
479      */

480     @SuppressWarnings JavaDoc("unchecked")
481     public void afterCommit()
482     {
483         if (logger.isDebugEnabled())
484         {
485             logger.debug("Processing end of transaction commit");
486         }
487         
488         TransactionData txnData = getTransactionData();
489         try
490         {
491             if (txnData.isClearOn)
492             {
493                 // clear shared cache
494
sharedCache.clear();
495                 if (logger.isDebugEnabled())
496                 {
497                     logger.debug("Clear notification recieved at end of transaction - clearing shared cache");
498                 }
499             }
500             else
501             {
502                 // transfer any removed items
503
// any removed items will have also been removed from the in-transaction updates
504
// propogate the deletes to the shared cache
505
List JavaDoc<Serializable JavaDoc> keys = txnData.removedItemsCache.getKeys();
506                 for (Serializable JavaDoc key : keys)
507                 {
508                     sharedCache.remove(key);
509                 }
510                 if (logger.isDebugEnabled())
511                 {
512                     logger.debug("Removed " + keys.size() + " values from shared cache");
513                 }
514             }
515
516             // transfer updates
517
List JavaDoc<Serializable JavaDoc> keys = txnData.updatedItemsCache.getKeys();
518             for (Serializable JavaDoc key : keys)
519             {
520                 Element element = txnData.updatedItemsCache.get(key);
521                 sharedCache.put(key, element.getValue());
522             }
523             if (logger.isDebugEnabled())
524             {
525                 logger.debug("Added " + keys.size() + " values to shared cache");
526             }
527         }
528         catch (CacheException e)
529         {
530             throw new AlfrescoRuntimeException("Failed to transfer updates to shared cache", e);
531         }
532         finally
533         {
534             removeCaches(txnData);
535         }
536     }
537
538     /**
539      * Just allow the transactional caches to be thrown away
540      */

541     public void afterRollback()
542     {
543         TransactionData txnData = getTransactionData();
544         // drop caches from cachemanager
545
removeCaches(txnData);
546     }
547     
548     /**
549      * Ensures that the transactional caches are removed from the common cache manager.
550      *
551      * @param txnData the data with references to the the transactional caches
552      */

553     private void removeCaches(TransactionData txnData)
554     {
555         cacheManager.removeCache(txnData.updatedItemsCache.getName());
556         cacheManager.removeCache(txnData.removedItemsCache.getName());
557     }
558     
559     /** Data holder to bind data to the transaction */
560     private class TransactionData
561     {
562         public Cache updatedItemsCache;
563         public Cache removedItemsCache;
564         public boolean isClearOn;
565     }
566 }
567
Popular Tags