KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * $Header: /home/cvs/jakarta-slide/src/share/org/apache/slide/util/ByteSizeLimitedObjectCache.java,v 1.5 2004/07/28 09:34:29 ib Exp $
3  * $Revision: 1.5 $
4  * $Date: 2004/07/28 09:34:29 $
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.Map JavaDoc;
29 import java.util.Set JavaDoc;
30 import java.util.Iterator JavaDoc;
31
32 import org.apache.slide.util.logger.Logger;
33
34 import org.apache.commons.collections.LRUMap;
35
36 /**
37  * Transactional LRU byte counting object cache. Caches objects using a least-recently-used strategy.
38  * Asserts a given overall size of all cached objects does not exceed a specified limit.
39  *
40  * It provides basic isolation from other transactions and atomicity of all operations. As
41  * no locking is used (which is undesirable mainly as a cache should never block and a commit must never fail)
42  * serializability needs to be guaranteed by underlying stores.
43  *
44  * <em>Note</em>: Unlike {@link TxLRUObjectCache} this cache also limits the size of temporary caches inside transactions.
45  * This is necessary as size of bytes in a transaction can become really big. In case an entry can not be cached inisde a
46  * transaction it will be marked as invalid after commit of that tx.
47  *
48  * @version $Revision: 1.5 $
49  * @see TxLRUObjectCache
50  */

51 public class ByteSizeLimitedObjectCache extends TxLRUObjectCache {
52
53     // amount of entries that are at most removed from cache to make room for a new entry
54
protected static final int MAX_FREEING_TRIES = 10;
55
56     protected long globalByteSize;
57     protected int txCacheSize;
58     protected long txByteSize;
59     protected long maxByteSizePerEntry;
60
61     /**
62      * Creates a new object cache. The maximum cache size can be configured for local transaction
63      * caches as well as on a global level.
64      *
65      * The idea of having a maximum size in bytes per entry is to prevent a large entry
66      * to displace many small entries.
67      *
68      * @param globalCacheSize maximum size in objects of global cache
69      * @param txCacheSize maximum size in objects for local transaction cache
70      * @param globalByteSize maximum size in bytes of global cache
71      * @param txByteSize maximum size in bytes for local transaction cache
72      * @param maxByteSizePerEntry maximum size of a single cache entry in bytes
73      * @param name the name used to construct logging category / channel
74      * @param logger Slide logger to be used for logging
75      * @param noGlobalCachingInsideTx indicates global caches are enabled, but shall not be used inside transactions
76      */

77     public ByteSizeLimitedObjectCache(
78         int globalCacheSize,
79         int txCacheSize,
80         long globalByteSize,
81         long txByteSize,
82         long maxByteSizePerEntry,
83         String JavaDoc name,
84         Logger logger,
85         boolean noGlobalCachingInsideTx) {
86         super(globalCacheSize, name, logger, noGlobalCachingInsideTx);
87         globalCache = new SizeCountingLRUMap(globalCacheSize, globalByteSize, maxByteSizePerEntry);
88         this.globalByteSize = globalByteSize;
89         this.txCacheSize = txCacheSize;
90         this.txByteSize = txByteSize;
91         this.maxByteSizePerEntry = maxByteSizePerEntry;
92         logChannel = "ByteSizeLimitedObjectCache";
93         if (name != null) {
94             logChannel += "." + name;
95         }
96
97     }
98
99     public synchronized void clear() {
100         super.clear();
101     }
102
103     public synchronized boolean canCache(Object JavaDoc txId, long byteSize) {
104         long maxSize;
105         if (txId != null) {
106             maxSize = globalByteSize;
107         } else {
108             maxSize = txByteSize;
109         }
110         return (maxSize >= byteSize && maxByteSizePerEntry >= byteSize);
111     }
112
113     public synchronized void put(Object JavaDoc txId, Object JavaDoc key, Object JavaDoc value, long byteSize) {
114         if (key == null) {
115             logger.log(txId + " adding null key with byte size " + byteSize, logChannel, Logger.WARNING);
116         }
117
118         if (txId != null) {
119             // if it has been deleted before, undo this
120
Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
121             deleteCache.remove(key);
122
123             SizeCountingLRUMap changeCache = (SizeCountingLRUMap) txChangeCaches.get(txId);
124             changeCache.put(key, value, byteSize);
125
126             if (loggingEnabled) {
127                 logger.log(txId + " added '" + key + "' with byte size " + byteSize, logChannel, Logger.DEBUG);
128             }
129         } else {
130             ((SizeCountingLRUMap) globalCache).put(key, value, byteSize);
131             if (loggingEnabled) {
132                 logger.log("Added '" + key + "' with byte size " + byteSize, logChannel, Logger.DEBUG);
133             }
134         }
135     }
136
137     public synchronized void start(Object JavaDoc txId) {
138         if (txId != null) {
139             txChangeCaches.put(txId, new SizeCountingLRUMap(txCacheSize, txByteSize, maxByteSizePerEntry, txId));
140             txDeleteCaches.put(txId, new HashSet JavaDoc());
141         }
142     }
143
144     public synchronized void commit(Object JavaDoc txId) {
145         if (txId != null) {
146             // apply local changes and deletes (is atomic as we have a global lock on this TxCache)
147

148             SizeCountingLRUMap changeCache = (SizeCountingLRUMap) txChangeCaches.get(txId);
149             for (Iterator it = changeCache.entrySet().iterator(); it.hasNext();) {
150                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
151                 long byteSize = changeCache.getByteSize(entry.getKey());
152                 if (byteSize == -1L) {
153                     globalCache.put(entry.getKey(), entry.getValue());
154                 } else {
155                     ((SizeCountingLRUMap) globalCache).put(entry.getKey(), entry.getValue(), byteSize);
156                 }
157             }
158
159             Set JavaDoc deleteCache = (Set JavaDoc) txDeleteCaches.get(txId);
160             for (Iterator it = deleteCache.iterator(); it.hasNext();) {
161                 Object JavaDoc key = it.next();
162                 globalCache.remove(key);
163             }
164
165             if (loggingEnabled) {
166                 logger.log(
167                     txId
168                         + " committed "
169                         + changeCache.size()
170                         + " changes and "
171                         + deleteCache.size()
172                         + " scheduled deletes",
173                     logChannel,
174                     Logger.DEBUG);
175             }
176
177             // finally forget about tx
178
forget(txId);
179         }
180     }
181
182     protected class SizeCountingLRUMap extends LRUMap {
183
184         private Map JavaDoc shadowSizes;
185         private long globalSize;
186         private long maxByteSize;
187         private long maxByteSizePerEntry;
188         private Object JavaDoc txId;
189
190         public SizeCountingLRUMap(int size, long maxByteSize, long maxByteSizePerEntry, Object JavaDoc txId) {
191             this(size, maxByteSize, maxByteSizePerEntry);
192             this.txId = txId;
193         }
194
195         public SizeCountingLRUMap(int size, long maxByteSize, long maxByteSizePerEntry) {
196             super(size);
197             this.shadowSizes = new HashMap();
198             this.globalSize = 0;
199             this.maxByteSize = maxByteSize;
200             this.maxByteSizePerEntry = maxByteSizePerEntry;
201         }
202
203         public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value, long byteSize) {
204             // is it too big to be cached?
205
if (byteSize > maxByteSizePerEntry || byteSize > maxByteSize) {
206                 if (loggingEnabled) {
207                     logger.log(txId + " for '" + key + "' is too big to be cached", logChannel, Logger.DEBUG);
208                 }
209                 Object JavaDoc oldValue = get(key);
210                 // invalidate previous entry if present
211
// XXX this relies on an implementation detail in TxLRUByteCache.put
212
// removal from delete cache must be done before trying to add to
213
// change cache using this method; if not our undoing will be partly
214
// undone (again) in TxLRUObjectCache.put
215
invalidate(key);
216                 return oldValue;
217             } else {
218                 // be sure to return allocated bytes before readding
219
freeBytes(key);
220
221                 // ok, we decided to cache this entry, make room for it
222
for (int i = 0; globalSize + byteSize > maxByteSize && i < MAX_FREEING_TRIES; i++) {
223                     if (loggingEnabled) {
224                         logger.log(txId + " for '" + key + "' needs "
225                                 + Long.toString(globalSize + byteSize - maxByteSize)
226                                 + " bytes more to be cached. Freeing bytes!", logChannel, Logger.DEBUG);
227                     }
228                     // this will call back processRemovedLRU and will thus free
229
// bytes
230
removeLRU();
231                 }
232             }
233             // was this successful?
234
if (globalSize + byteSize <= maxByteSize) {
235                 shadowSizes.put(key, new Long JavaDoc(byteSize));
236                 globalSize += byteSize;
237                 return super.put(key, value);
238             } else {
239                 Object JavaDoc oldValue = get(key);
240                 invalidate(key);
241                 return oldValue;
242             }
243         }
244
245         public long getByteSize(Object JavaDoc key) {
246             Long JavaDoc lSize = (Long JavaDoc) shadowSizes.get(key);
247             if (lSize != null) {
248                 return lSize.longValue();
249             } else {
250                 return -1L;
251             }
252         }
253
254         protected void freeBytes(Object JavaDoc key) {
255             Long JavaDoc lSize = (Long JavaDoc) shadowSizes.remove(key);
256             if (lSize != null) {
257                 long size = lSize.longValue();
258                 globalSize -= size;
259             }
260         }
261
262         protected void invalidate(Object JavaDoc key) {
263             // if it is no longer cached, it still means entry currently
264
// in global map gets invalid upon commit of this tx
265
freeBytes(key);
266             ByteSizeLimitedObjectCache.this.remove(txId, key);
267             if (loggingEnabled) {
268                 logger.log(txId + " invalidated '" + key + "'", logChannel, Logger.DEBUG);
269             }
270         }
271
272         // notify cache that this entry has been removed by LRU strategy (called back by LRUMap)
273
protected void processRemovedLRU(Object JavaDoc key, Object JavaDoc value) {
274             invalidate(key);
275         }
276     }
277 }
278
Popular Tags