Coverage Report - org.apache.ojb.broker.cache.ObjectCacheDefaultImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ObjectCacheDefaultImpl
N/A
N/A
2.1
ObjectCacheDefaultImpl$CacheEntry
N/A
N/A
2.1
ObjectCacheDefaultImpl$CacheEntryHard
N/A
N/A
2.1
ObjectCacheDefaultImpl$CacheEntrySoft
N/A
N/A
2.1
ObjectCacheDefaultImpl$OrderedTuple
N/A
N/A
2.1
 
 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;
 19  
 import java.lang.ref.SoftReference;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Hashtable;
 22  
 import java.util.Iterator;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 import java.util.Properties;
 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.1 2007-08-24 22:17:29 ewestfal Exp $
 128  
  */
 129  
 public class ObjectCacheDefaultImpl implements ObjectCacheInternal, PBStateListener
 130  
 {
 131  
     private Logger log = LoggerFactory.getLogger(ObjectCacheDefaultImpl.class);
 132  
 
 133  
     public static final String TIMEOUT_PROP = "timeout";
 134  
     public static final String AUTOSYNC_PROP = "autoSync";
 135  
     public static final String CACHING_KEY_TYPE_PROP = "cachingKeyType";
 136  
     public static final String SOFT_REFERENCES_PROP = "useSoftReferences";
 137  
     /**
 138  
      * static Map held all cached objects
 139  
      */
 140  
     protected static final Map objectTable = new Hashtable();
 141  
     private static final ReferenceQueue queue = new ReferenceQueue();
 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 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 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();
 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 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 obj)
 226  
     {
 227  
         doInternalCache(oid, obj, ObjectCacheInternal.TYPE_UNKNOWN);
 228  
     }
 229  
 
 230  
     public boolean cacheIfNew(Identity oid, Object obj)
 231  
     {
 232  
         //processQueue();
 233  
         boolean result = false;
 234  
         Object 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 lookup(Identity oid)
 255  
     {
 256  
         processQueue();
 257  
         hitCount++;
 258  
         Object 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 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 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 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 buildKey(Identity oid)
 409  
     {
 410  
         Object 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(broker.getDescriptorRepository().hashCode()));
 427  
                 break;
 428  
             case 3:
 429  
                 key = new OrderedTuple(oid, broker.getPBKey().getAlias(),
 430  
                         new Integer(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[] elements;
 455  
         private int hashCode;
 456  
 
 457  
         public OrderedTuple(Object element)
 458  
         {
 459  
             elements = new Object[1];
 460  
             elements[0] = element;
 461  
             hashCode = calcHashCode();
 462  
         }
 463  
 
 464  
         public OrderedTuple(Object element1, Object element2)
 465  
         {
 466  
             elements = new Object[2];
 467  
             elements[0] = element1;
 468  
             elements[1] = element2;
 469  
             hashCode = calcHashCode();
 470  
         }
 471  
 
 472  
         public OrderedTuple(Object element1, Object element2, Object element3)
 473  
         {
 474  
             elements = new Object[3];
 475  
             elements[0] = element1;
 476  
             elements[1] = element2;
 477  
             elements[2] = element3;
 478  
             hashCode = calcHashCode();
 479  
         }
 480  
 
 481  
         public OrderedTuple(Object[] 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 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 toString()
 535  
         {
 536  
             StringBuffer s = new StringBuffer();
 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 get();
 554  
         Identity getOid();
 555  
         long getLifetime();
 556  
     }
 557  
 
 558  
     final static class CacheEntrySoft extends SoftReference implements CacheEntry
 559  
     {
 560  
         private final long lifetime;
 561  
         private final Identity oid;
 562  
 
 563  
         CacheEntrySoft(Object object, final Identity k, final ReferenceQueue 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 obj;
 594  
 
 595  
         CacheEntryHard(Object 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 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  
 }