KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > slide > util > TxLRUObjectCache


1 /*
2  * $Header: /home/cvs/jakarta-slide/src/share/org/apache/slide/util/TxLRUObjectCache.java,v 1.6.2.2 2004/10/19 07:37:21 ozeigermann Exp $
3  * $Revision: 1.6.2.2 $
4  * $Date: 2004/10/19 07:37:21 $
5  *
6  * ====================================================================
7  *
8  * Copyright 1999-2002 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  *
22  */

23
24 package org.apache.slide.util;
25
26 import java.util.HashMap JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31
32 import org.apache.slide.util.logger.Logger;
33
34 import org.apache.commons.collections.LRUMap;
35
36 /**
37  * Transactional LRU object cache. Caches objects using a least-recently-used strategy.
38  *
39  * It provides basic isolation from other transactions and atomicity of all operations. As
40  * no locking is used (which is undesirable mainly as a cache should never block and a commit must never fail)
41  * serializability needs to be guaranteed by underlying stores.
42  * <br>
43  * <br>
44  * <em>Caution</em>: Only global caches are limited by given size.
45  * Size of temporary data inside a transaction is unlimited.
46  * <br>
47  * <br>
48  * <em>Note</em>: This cache has no idea if the data it caches in transactions are read from or written to store.
49  * It thus handles both access types the same way. This means read accesses are cached in transactions even though they
50  * could be cached globally. Like write accesses they will be moved to global cache at commit time.
51  *
52  * @version $Revision: 1.6.2.2 $
53  */

54 public class TxLRUObjectCache {
55
56     protected Map JavaDoc globalCache = null;
57
58     protected Map JavaDoc txChangeCaches;
59     protected Map JavaDoc txDeleteCaches;
60
61     protected int hits = 0;
62     protected int misses = 0;
63
64     protected String JavaDoc name;
65     protected Logger logger;
66     protected String JavaDoc logChannel;
67     protected final boolean loggingEnabled;
68     
69     protected boolean noGlobalCachingInsideTx;
70
71     /**
72      * Creates a new object cache. If global caching is disabled, the cache
73      * reflects isolation of underlying store as only double reads inside a single transaction
74      * will be cached. This may even increase isolation as repeatable read is guaranteed.
75      *
76      * @param globalCacheSize maximum size in objects of global cache or <code>-1</code> to indicate no
77      * global cache shall be used
78      * @param name the name used to construct logging category / channel
79      * @param logger Slide logger to be used for logging
80      * @param noGlobalCachingInsideTx indicates global caches are enabled, but shall not be used inside transactions
81      */

82     public TxLRUObjectCache(int globalCacheSize, String JavaDoc name, Logger logger, boolean noGlobalCachingInsideTx) {
83         if (globalCacheSize != -1) {
84             globalCache = new LRUMap(globalCacheSize);
85         }
86         txChangeCaches = new HashMap();
87         txDeleteCaches = new HashMap();
88
89         this.name = name;
90         this.logger = logger;
91         this.noGlobalCachingInsideTx = noGlobalCachingInsideTx;
92
93         logChannel = "TxLRUObjectCache";
94         if (name != null) {
95             logChannel += "." + name;
96         }
97
98         // used for guarded logging as preparation is expensive
99
loggingEnabled = logger.isEnabled(logChannel, Logger.DEBUG);
100     }
101
102     public synchronized void clear() {
103         if (globalCache != null) globalCache.clear();
104         txChangeCaches.clear();
105         txDeleteCaches.clear();
106     }
107
108     public synchronized Object JavaDoc get(Object JavaDoc txId, Object JavaDoc key) {
109         if (txId != null) {
110             Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
111             if (deleteCache.contains(key)) {
112                 hit(txId, key);
113                 // reflects that entry has been deleted in this tx
114
return null;
115             }
116
117             Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
118             Object JavaDoc changed = changeCache.get(key);
119             if (changed != null) {
120                 hit(txId, key);
121                 // if object has been changed in this tx, get the local one
122
return changed;
123             }
124         }
125         
126         if (globalCache == null) return null;
127         
128         // if global cache is disabled inside transactions, do not use it
129
if (noGlobalCachingInsideTx && txId != null) return null;
130         
131         // as fall back return value from global cache (if present)
132
Object JavaDoc global = globalCache.get(key);
133         if (global != null) {
134             hit(txId, key);
135         } else {
136             miss(txId, key);
137         }
138         return global;
139     }
140
141     public synchronized void put(Object JavaDoc txId, Object JavaDoc key, Object JavaDoc value) {
142         if (txId != null) {
143             // if it has been deleted before, undo this
144
Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
145             deleteCache.remove(key);
146
147             Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
148             changeCache.put(key, value);
149
150             if (loggingEnabled) {
151                 logger.log(txId + " added '" + key + "'", logChannel, Logger.DEBUG);
152             }
153         } else {
154             if (globalCache != null) {
155                 globalCache.put(key, value);
156                 if (loggingEnabled) {
157                     logger.log("Added '" + key + "'", logChannel, Logger.DEBUG);
158                 }
159             }
160         }
161     }
162
163     public synchronized void remove(Object JavaDoc txId, Object JavaDoc key) {
164         if (txId != null) {
165             // if it has been changed before, undo this
166
Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
167             changeCache.remove(key);
168
169             Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
170             deleteCache.add(key);
171
172             // guard logging as preparation is expensive
173
if (loggingEnabled) {
174                 logger.log(txId + " removed '" + key + "'", logChannel, Logger.DEBUG);
175             }
176         } else {
177             if (globalCache != null) {
178                 globalCache.remove(key);
179                 if (loggingEnabled) {
180                     logger.log("Removed '" + key + "'", logChannel, Logger.DEBUG);
181                 }
182             }
183         }
184     }
185     
186     /**
187      * <p>
188      * Removes the object identified by <code>key</code> as well as any objects
189      * identified by <code>key.toString() + delimiter</code>.
190      * </p>
191      * <p>
192      * Example: <code>remove(xId, "/slide/files", "-")</code> would remove
193      * <code>/slide/files</code> and <code>/slide/files-1.3</code> but not
194      * <code>/slide/files/temp</code>.
195      *
196      * @param txId the id of the current transaction or <code>null</code> if not
197      * in a transaction.
198      * @param key the key to remove from the cache.
199      * @param delimiter the delimiter to use to identify subnodes that should be
200      * removed as well.
201      */

202     public synchronized void remove(Object JavaDoc txId, Object JavaDoc key, String JavaDoc delimiter) {
203         if (txId != null) {
204             // undo any changes
205
Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
206             changeCache.remove(key);
207             prune(changeCache, key, delimiter);
208
209             Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
210             deleteCache.add(key);
211             deprune(deleteCache, key, delimiter);
212
213             // guard logging as preparation is expensive
214
if (loggingEnabled) {
215                 logger.log(txId + " removed '" + key + "'", logChannel, Logger.DEBUG);
216             }
217         } else {
218             if (globalCache != null) {
219                 globalCache.remove(key);
220                 prune(globalCache, key, delimiter);
221                 if (loggingEnabled) {
222                     logger.log("Removed '" + key + "'", logChannel, Logger.DEBUG);
223                 }
224             }
225         }
226     }
227
228     public synchronized void start(Object JavaDoc txId) {
229         if (txId != null) {
230             txChangeCaches.put(txId, new HashMap());
231             txDeleteCaches.put(txId, new HashSet JavaDoc());
232         }
233     }
234
235     public synchronized void rollback(Object JavaDoc txId) {
236         if (txId != null) {
237
238             if (loggingEnabled) {
239                 Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
240                 Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
241                 logger.log(
242                     txId
243                         + " rolled back "
244                         + changeCache.size()
245                         + " changes and "
246                         + deleteCache.size()
247                         + " scheduled deletes",
248                     logChannel,
249                     Logger.DEBUG);
250             }
251
252             // simply forget about tx
253
forget(txId);
254         }
255     }
256
257     public synchronized void commit(Object JavaDoc txId) {
258         if (txId != null) {
259
260             if (globalCache != null) {
261                 // apply local changes and deletes (is atomic as we have a global lock on this TxCache)
262

263                 Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
264                 for (Iterator it = changeCache.entrySet().iterator(); it.hasNext();) {
265                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
266                     globalCache.put(entry.getKey(), entry.getValue());
267                 }
268
269                 Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
270                 for (Iterator it = deleteCache.iterator(); it.hasNext();) {
271                     Object JavaDoc key = it.next();
272                     globalCache.remove(key);
273                 }
274
275                 if (loggingEnabled) {
276                     logger.log(
277                         txId
278                             + " committed "
279                             + changeCache.size()
280                             + " changes and "
281                             + deleteCache.size()
282                             + " scheduled deletes",
283                         logChannel,
284                         Logger.DEBUG);
285                 }
286             }
287             // finally forget about tx
288
forget(txId);
289         }
290     }
291
292     public synchronized void forget(Object JavaDoc txId) {
293         if (txId != null) {
294             txChangeCaches.remove(txId);
295             txDeleteCaches.remove(txId);
296         }
297     }
298
299     protected void hit(Object JavaDoc txId, Object JavaDoc key) {
300         hits++;
301         log(txId, key, true);
302     }
303
304     protected void miss(Object JavaDoc txId, Object JavaDoc key) {
305         misses++;
306         log(txId, key, false);
307     }
308
309     protected void log(Object JavaDoc txId, Object JavaDoc key, boolean hit) {
310         if (loggingEnabled) {
311             StringBuffer JavaDoc log = new StringBuffer JavaDoc();
312
313             if (txId != null) {
314                 Map JavaDoc changeCache = (Map JavaDoc) txChangeCaches.get(txId);
315                 Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
316                 log.append(txId + " (" + changeCache.size() + ", " + deleteCache.size() + ") ");
317             }
318
319             log.append(
320                 (hit ? "Cache Hit: '" : "Cache Miss: '")
321                     + key
322                     + "' "
323                     + hits
324                     + " / "
325                     + misses
326                     + " / "
327                     + (globalCache != null ? globalCache.size() : -1));
328             logger.log(log.toString(), logChannel, Logger.DEBUG);
329         }
330     }
331     
332     protected void prune(Map JavaDoc map, Object JavaDoc key, String JavaDoc delimiter) {
333         for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
334             Map.Entry JavaDoc entry = (Map.Entry JavaDoc)it.next();
335             if (entry.getKey().toString().startsWith(key + delimiter)) {
336                 it.remove();
337             }
338         }
339     }
340     
341     protected void deprune(Set JavaDoc set, Object JavaDoc key, String JavaDoc delimiter) {
342         if (globalCache != null) {
343             for (Iterator it = globalCache.entrySet().iterator(); it.hasNext();) {
344                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
345                 if (entry.getKey().toString().startsWith(key + delimiter)) {
346                     set.add(entry.getKey());
347                 }
348             }
349         }
350     }
351 }
352
Popular Tags