KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > ojb > broker > cache > ObjectCacheDefaultImpl


1 package org.apache.ojb.broker.cache;
2
3 /* Copyright 2004-2005 The Apache Software Foundation
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 import java.lang.ref.ReferenceQueue JavaDoc;
19 import java.lang.ref.SoftReference JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Hashtable JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.Properties JavaDoc;
26
27 import org.apache.commons.lang.builder.ToStringBuilder;
28 import org.apache.commons.lang.builder.ToStringStyle;
29 import org.apache.ojb.broker.Identity;
30 import org.apache.ojb.broker.OJBRuntimeException;
31 import org.apache.ojb.broker.PBStateEvent;
32 import org.apache.ojb.broker.PBStateListener;
33 import org.apache.ojb.broker.PersistenceBroker;
34 import org.apache.ojb.broker.util.logging.Logger;
35 import org.apache.ojb.broker.util.logging.LoggerFactory;
36
37 /**
38  * This global ObjectCache stores all Objects loaded by the <code>PersistenceBroker</code>
39  * from a DB using a static {@link java.util.Map}. This means each {@link ObjectCache}
40  * instance associated with all {@link PersistenceBroker} instances use the same
41  * <code>Map</code> to cache objects. This could lead in "dirty-reads" (similar to read-uncommitted
42  * mode in DB) when a concurrent thread look up same object modified by another thread.
43  * <br/>
44  * When the PersistenceBroker tries to get an Object by its {@link Identity}.
45  * It first lookups the cache if the object has been already loaded and cached.
46  * <p/>
47  * NOTE: By default objects cached via {@link SoftReference} which allows
48  * objects (softly) referenced by the cache to be reclaimed by the Java Garbage Collector when
49  * they are not longer referenced elsewhere, so lifetime of cached object is limited by
50  * <br/> - the lifetime of the cache object - see property <code>timeout</code>.
51  * <br/> - the garabage collector used memory settings - see property <code>useSoftReferences</code>.
52  * <br/> - the maximum capacity of the cache - see property <code>maxEntry</code>.
53  * </p>
54  * <p/>
55  * Implementation configuration properties:
56  * </p>
57  * <p/>
58  * <p/>
59  * <table cellspacing="2" cellpadding="2" border="3" frame="box">
60  * <tr>
61  * <td><strong>Property Key</strong></td>
62  * <td><strong>Property Values</strong></td>
63  * </tr>
64  * <p/>
65  * <tr>
66  * <td>timeout</td>
67  * <td>
68  * Lifetime of the cached objects in seconds.
69  * If expired the cached object was not returned
70  * on lookup call (and removed from cache). Default timeout
71  * value is 900 seconds. When set to <tt>-1</tt> the lifetime of
72  * the cached object depends only on GC and do never get timed out.
73  * </td>
74  * </tr>
75  * <p/>
76  * <tr>
77  * <td>autoSync</td>
78  * <td>
79  * If set <tt>true</tt> all cached/looked up objects within a PB-transaction are traced.
80  * If the the PB-transaction was aborted all traced objects will be removed from
81  * cache. Default is <tt>false</tt>.
82  * <p/>
83  * NOTE: This does not prevent "dirty-reads" (more info see above).
84  * </p>
85  * <p/>
86  * It's not a smart solution for keeping cache in sync with DB but should do the job
87  * in most cases.
88  * <br/>
89  * E.g. if you lookup 1000 objects within a transaction and modify one object and then abort the
90  * transaction, 1000 objects will be passed to cache, 1000 objects will be traced and
91  * all 1000 objects will be removed from cache. If you read these objects without tx or
92  * in a former tx and then modify one object in a tx and abort the tx, only one object was
93  * traced/removed.
94  * </p>
95  * </td>
96  * </tr>
97  * <p/>
98  * <tr>
99  * <td>cachingKeyType</td>
100  * <td>
101  * Determines how the key was build for the cached objects:
102  * <br/>
103  * 0 - Identity object was used as key, this was the <em>default</em> setting.
104  * <br/>
105  * 1 - Idenity + jcdAlias name was used as key. Useful when the same object metadata model
106  * (DescriptorRepository instance) are used for different databases (JdbcConnectionDescriptor)
107  * <br/>
108  * 2 - Identity + model (DescriptorRepository) was used as key. Useful when different metadata
109  * model (DescriptorRepository instance) are used for the same database. Keep in mind that there
110  * was no synchronization between cached objects with same Identity but different metadata model.
111  * <br/>
112  * 3 - all together (1+2)
113  * </td>
114  * </tr>
115  * <p/>
116  * <tr>
117  * <td>useSoftReferences</td>
118  * <td>
119  * If set <em>true</em> this class use {@link java.lang.ref.SoftReference} to cache
120  * objects. Default value is <em>true</em>.
121  * </td>
122  * </tr>
123  * </table>
124  * <p/>
125  *
126  * @author <a HREF="mailto:thma@apache.org">Thomas Mahler<a>
127  * @version $Id: ObjectCacheDefaultImpl.java,v 1.24.2.7 2005/12/21 22:24:15 tomdz Exp $
128  */

129 public class ObjectCacheDefaultImpl implements ObjectCacheInternal, PBStateListener
130 {
131     private Logger log = LoggerFactory.getLogger(ObjectCacheDefaultImpl.class);
132
133     public static final String JavaDoc TIMEOUT_PROP = "timeout";
134     public static final String JavaDoc AUTOSYNC_PROP = "autoSync";
135     public static final String JavaDoc CACHING_KEY_TYPE_PROP = "cachingKeyType";
136     public static final String JavaDoc SOFT_REFERENCES_PROP = "useSoftReferences";
137     /**
138      * static Map held all cached objects
139      */

140     protected static final Map JavaDoc objectTable = new Hashtable JavaDoc();
141     private static final ReferenceQueue JavaDoc queue = new ReferenceQueue JavaDoc();
142
143     private static long hitCount = 0;
144     private static long failCount = 0;
145     private static long gcCount = 0;
146
147     protected PersistenceBroker broker;
148     private List JavaDoc identitiesInWork;
149     /**
150      * Timeout of the cached objects. Default was 900 seconds.
151      */

152     private long timeout = 1000 * 60 * 15;
153     private boolean useAutoSync = false;
154     /**
155      * Determines how the key was build for the cached objects:
156      * <br/>
157      * 0 - Identity object was used as key
158      * 1 - Idenity + jcdAlias name was used as key
159      * 2 - Identity + model (DescriptorRepository) was used as key
160      * 3 - all together (1+2)
161      */

162     private int cachingKeyType;
163     private boolean useSoftReferences = true;
164
165     public ObjectCacheDefaultImpl(PersistenceBroker broker, Properties JavaDoc prop)
166     {
167         this.broker = broker;
168         timeout = prop == null ? timeout : (Long.parseLong(prop.getProperty(TIMEOUT_PROP, "" + (60 * 15))) * 1000);
169         useSoftReferences = prop != null && (Boolean.valueOf((prop.getProperty(SOFT_REFERENCES_PROP, "true")).trim())).booleanValue();
170         cachingKeyType = prop == null ? 0 : (Integer.parseInt(prop.getProperty(CACHING_KEY_TYPE_PROP, "0")));
171         useAutoSync = prop != null && (Boolean.valueOf((prop.getProperty(AUTOSYNC_PROP, "false")).trim())).booleanValue();
172         if(useAutoSync)
173         {
174             if(broker != null)
175             {
176                 // we add this instance as a permanent PBStateListener
177
broker.addListener(this, true);
178             }
179             else
180             {
181                 log.info("Can't enable property '" + AUTOSYNC_PROP + "', because given PB instance is null");
182             }
183         }
184         identitiesInWork = new ArrayList JavaDoc();
185         if(log.isEnabledFor(Logger.INFO))
186         {
187             ToStringBuilder buf = new ToStringBuilder(this);
188             buf.append("timeout", timeout)
189                     .append("useSoftReferences", useSoftReferences)
190                     .append("cachingKeyType", cachingKeyType)
191                     .append("useAutoSync", useAutoSync);
192             log.info("Setup cache: " + buf.toString());
193         }
194     }
195
196     /**
197      * Clear ObjectCache. I.e. remove all entries for classes and objects.
198      */

199     public void clear()
200     {
201         //processQueue();
202
objectTable.clear();
203         identitiesInWork.clear();
204     }
205
206     public void doInternalCache(Identity oid, Object JavaDoc obj, int type)
207     {
208         //processQueue();
209
if((obj != null))
210         {
211             traceIdentity(oid);
212             synchronized(objectTable)
213             {
214                 if(log.isDebugEnabled()) log.debug("Cache object " + oid);
215                 objectTable.put(buildKey(oid), buildEntry(obj, oid));
216             }
217         }
218     }
219
220     /**
221      * Makes object persistent to the Objectcache.
222      * I'm using soft-references to allow gc reclaim unused objects
223      * even if they are still cached.
224      */

225     public void cache(Identity oid, Object JavaDoc obj)
226     {
227         doInternalCache(oid, obj, ObjectCacheInternal.TYPE_UNKNOWN);
228     }
229
230     public boolean cacheIfNew(Identity oid, Object JavaDoc obj)
231     {
232         //processQueue();
233
boolean result = false;
234         Object JavaDoc key = buildKey(oid);
235         if((obj != null))
236         {
237             synchronized(objectTable)
238             {
239                 if(!objectTable.containsKey(key))
240                 {
241                     objectTable.put(key, buildEntry(obj, oid));
242                     result = true;
243                 }
244             }
245             if(result) traceIdentity(oid);
246         }
247         return result;
248     }
249
250     /**
251      * Lookup object with Identity oid in objectTable.
252      * Returns null if no matching id is found
253      */

254     public Object JavaDoc lookup(Identity oid)
255     {
256         processQueue();
257         hitCount++;
258         Object JavaDoc result = null;
259
260         CacheEntry entry = (CacheEntry) objectTable.get(buildKey(oid));
261         if(entry != null)
262         {
263             result = entry.get();
264             if(result == null || entry.getLifetime() < System.currentTimeMillis())
265             {
266                 /*
267                 cached object was removed by gc or lifetime was exhausted
268                 remove CacheEntry from map
269                 */

270                 gcCount++;
271                 remove(oid);
272                 // make sure that we return null
273
result = null;
274             }
275             else
276             {
277                 /*
278                 TODO: Not sure if this makes sense, could help to avoid corrupted objects
279                 when changed in tx but not stored.
280                 */

281                 traceIdentity(oid);
282                 if(log.isDebugEnabled()) log.debug("Object match " + oid);
283             }
284         }
285         else
286         {
287             failCount++;
288         }
289         return result;
290     }
291
292     /**
293      * Removes an Object from the cache.
294      */

295     public void remove(Identity oid)
296     {
297         //processQueue();
298
if(oid != null)
299         {
300             removeTracedIdentity(oid);
301             objectTable.remove(buildKey(oid));
302             if(log.isDebugEnabled()) log.debug("Remove object " + oid);
303         }
304     }
305
306     public String JavaDoc toString()
307     {
308         ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE);
309         buf.append("Count of cached objects", objectTable.keySet().size());
310         buf.append("Lookup hits", hitCount);
311         buf.append("Failures", failCount);
312         buf.append("Reclaimed", gcCount);
313         return buf.toString();
314     }
315
316     private void traceIdentity(Identity oid)
317     {
318         if(useAutoSync && (broker != null) && broker.isInTransaction())
319         {
320             identitiesInWork.add(oid);
321         }
322     }
323
324     private void removeTracedIdentity(Identity oid)
325     {
326         identitiesInWork.remove(oid);
327     }
328
329     private void synchronizeWithTracedObjects()
330     {
331         Identity oid;
332         log.info("tx was aborted," +
333                 " remove " + identitiesInWork.size() + " traced (potentially modified) objects from cache");
334         for(Iterator JavaDoc iterator = identitiesInWork.iterator(); iterator.hasNext();)
335         {
336             oid = (Identity) iterator.next();
337             objectTable.remove(buildKey(oid));
338         }
339     }
340
341     public void beforeRollback(PBStateEvent event)
342     {
343         synchronizeWithTracedObjects();
344         identitiesInWork.clear();
345     }
346
347     public void beforeCommit(PBStateEvent event)
348     {
349         // identitiesInWork.clear();
350
}
351
352     public void beforeClose(PBStateEvent event)
353     {
354         /*
355         arminw: In managed environments listener method "beforeClose" is called twice
356         (when the PB handle is closed and when the real PB instance is closed/returned to pool).
357         We are only interested in the real close call when all work is done.
358         */

359         if(!broker.isInTransaction())
360         {
361             identitiesInWork.clear();
362         }
363     }
364
365     public void afterRollback(PBStateEvent event)
366     {
367     }
368
369     public void afterCommit(PBStateEvent event)
370     {
371         identitiesInWork.clear();
372     }
373
374     public void afterBegin(PBStateEvent event)
375     {
376     }
377
378     public void beforeBegin(PBStateEvent event)
379     {
380     }
381
382     public void afterOpen(PBStateEvent event)
383     {
384     }
385
386     private CacheEntry buildEntry(Object JavaDoc obj, Identity oid)
387     {
388         if(useSoftReferences)
389         {
390             return new CacheEntrySoft(obj, oid, queue, timeout);
391         }
392         else
393         {
394             return new CacheEntryHard(obj, oid, timeout);
395         }
396     }
397
398     private void processQueue()
399     {
400         CacheEntry sv;
401         while((sv = (CacheEntry) queue.poll()) != null)
402         {
403             removeTracedIdentity(sv.getOid());
404             objectTable.remove(buildKey(sv.getOid()));
405         }
406     }
407
408     private Object JavaDoc buildKey(Identity oid)
409     {
410         Object JavaDoc key;
411         switch(cachingKeyType)
412         {
413             case 0:
414                 key = oid;
415                 break;
416             case 1:
417                 key = new OrderedTuple(oid, broker.getPBKey().getAlias());
418                 break;
419             case 2:
420                 /*
421                 this ObjectCache implementation only works in single JVM, so the hashCode
422                 of the DescriptorRepository class is unique
423                 TODO: problem when different versions of same DR are used
424                 */

425                 key = new OrderedTuple(oid,
426                         new Integer JavaDoc(broker.getDescriptorRepository().hashCode()));
427                 break;
428             case 3:
429                 key = new OrderedTuple(oid, broker.getPBKey().getAlias(),
430                         new Integer JavaDoc(broker.getDescriptorRepository().hashCode()));
431                 break;
432             default:
433                 throw new OJBRuntimeException("Unexpected error, 'cacheType =" + cachingKeyType + "' was not supported");
434         }
435         return key;
436     }
437
438
439     //-----------------------------------------------------------
440
// inner class to build unique key for cached objects
441
//-----------------------------------------------------------
442
/**
443      * Implements equals() and hashCode() for an ordered tuple of constant(!)
444      * objects
445      *
446      * @author Gerhard Grosse
447      * @since Oct 12, 2004
448      */

449     static final class OrderedTuple
450     {
451         private static int[] multipliers =
452                 new int[]{13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 51};
453
454         private Object JavaDoc[] elements;
455         private int hashCode;
456
457         public OrderedTuple(Object JavaDoc element)
458         {
459             elements = new Object JavaDoc[1];
460             elements[0] = element;
461             hashCode = calcHashCode();
462         }
463
464         public OrderedTuple(Object JavaDoc element1, Object JavaDoc element2)
465         {
466             elements = new Object JavaDoc[2];
467             elements[0] = element1;
468             elements[1] = element2;
469             hashCode = calcHashCode();
470         }
471
472         public OrderedTuple(Object JavaDoc element1, Object JavaDoc element2, Object JavaDoc element3)
473         {
474             elements = new Object JavaDoc[3];
475             elements[0] = element1;
476             elements[1] = element2;
477             elements[2] = element3;
478             hashCode = calcHashCode();
479         }
480
481         public OrderedTuple(Object JavaDoc[] elements)
482         {
483             this.elements = elements;
484             this.hashCode = calcHashCode();
485         }
486
487         private int calcHashCode()
488         {
489             int code = 7;
490             for(int i = 0; i < elements.length; i++)
491             {
492                 int m = i % multipliers.length;
493                 code += elements[i].hashCode() * multipliers[m];
494             }
495             return code;
496         }
497
498         public boolean equals(Object JavaDoc obj)
499         {
500             if(!(obj instanceof OrderedTuple))
501             {
502                 return false;
503             }
504             else
505             {
506                 OrderedTuple other = (OrderedTuple) obj;
507                 if(this.hashCode != other.hashCode)
508                 {
509                     return false;
510                 }
511                 else if(this.elements.length != other.elements.length)
512                 {
513                     return false;
514                 }
515                 else
516                 {
517                     for(int i = 0; i < elements.length; i++)
518                     {
519                         if(!this.elements[i].equals(other.elements[i]))
520                         {
521                             return false;
522                         }
523                     }
524                     return true;
525                 }
526             }
527         }
528
529         public int hashCode()
530         {
531             return hashCode;
532         }
533
534         public String JavaDoc toString()
535         {
536             StringBuffer JavaDoc s = new StringBuffer JavaDoc();
537             s.append('{');
538             for(int i = 0; i < elements.length; i++)
539             {
540                 s.append(elements[i]).append('#').append(elements[i].hashCode()).append(',');
541             }
542             s.setCharAt(s.length() - 1, '}');
543             s.append("#").append(hashCode);
544             return s.toString();
545         }
546     }
547
548     //-----------------------------------------------------------
549
// inner classes to wrap cached objects
550
//-----------------------------------------------------------
551
interface CacheEntry
552     {
553         Object JavaDoc get();
554         Identity getOid();
555         long getLifetime();
556     }
557
558     final static class CacheEntrySoft extends SoftReference JavaDoc implements CacheEntry
559     {
560         private final long lifetime;
561         private final Identity oid;
562
563         CacheEntrySoft(Object JavaDoc object, final Identity k, final ReferenceQueue JavaDoc q, long timeout)
564         {
565             super(object, q);
566             oid = k;
567             // if timeout is negative, lifetime of object never expire
568
if(timeout < 0)
569             {
570                 lifetime = Long.MAX_VALUE;
571             }
572             else
573             {
574                 lifetime = System.currentTimeMillis() + timeout;
575             }
576         }
577
578         public Identity getOid()
579         {
580             return oid;
581         }
582
583         public long getLifetime()
584         {
585             return lifetime;
586         }
587     }
588
589     final static class CacheEntryHard implements CacheEntry
590     {
591         private final long lifetime;
592         private final Identity oid;
593         private Object JavaDoc obj;
594
595         CacheEntryHard(Object JavaDoc object, final Identity k, long timeout)
596         {
597             obj = object;
598             oid = k;
599             // if timeout is negative, lifetime of object never expire
600
if(timeout < 0)
601             {
602                 lifetime = Long.MAX_VALUE;
603             }
604             else
605             {
606                 lifetime = System.currentTimeMillis() + timeout;
607             }
608         }
609
610         public Object JavaDoc get()
611         {
612             return obj;
613         }
614
615         public Identity getOid()
616         {
617             return oid;
618         }
619
620         public long getLifetime()
621         {
622             return lifetime;
623         }
624     }
625 }
626
Popular Tags