db-ojb-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From oliv...@apache.org
Subject cvs commit: db-ojb/src/test/org/apache/ojb/broker/cloneable CloneableGroup.java
Date Tue, 28 Oct 2003 09:15:20 GMT
oliverm     2003/10/28 01:15:20

  Modified:    src/test/org/apache/ojb repository.xml
  Added:       src/test/org/apache/ojb/broker/sqlcount
                        TwoLevelSimpleTest.java
               src/java/org/apache/ojb/broker/cache TwoLevelCache.java
               src/test/org/apache/ojb repository_junit_cloneable.xml
               src/test/org/apache/ojb/broker/cloneable CloneableGroup.java
  Log:
  no message
  
  Revision  Changes    Path
  1.1                  db-ojb/src/test/org/apache/ojb/broker/sqlcount/TwoLevelSimpleTest.java
  
  Index: TwoLevelSimpleTest.java
  ===================================================================
  /*
   * (C) 2003 ppi Media
   * User: om
   */
  
  package org.apache.ojb.broker.sqlcount;
  
  import org.apache.ojb.broker.util.configuration.impl.OjbConfiguration;
  import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
  import org.apache.ojb.broker.cache.ObjectCacheFactory;
  import org.apache.ojb.broker.cache.TwoLevelCache;
  import org.apache.ojb.broker.PersistenceBrokerFactory;
  import org.apache.ojb.broker.Identity;
  import org.apache.ojb.broker.PersistenceBroker;
  import org.apache.ojb.broker.cloneable.CloneableGroup;
  
  /**
   * @author <a href="mailto:om@ppi.de">Oliver Matz</a>
   * @version $Id: TwoLevelSimpleTest.java,v 1.1 2003/10/28 09:15:20 oliverm Exp $
   */
  public class TwoLevelSimpleTest
          extends AbstractCountTest
  {
    private Class old_ObjectCache;
    private String[] old_CacheFilter;
  
    private OjbConfiguration getConfig()
    {
        return (OjbConfiguration) OjbConfigurator.getInstance().getConfigurationFor(null);
    }
    /**
     * switch cache to {@link org.apache.ojb.broker.cache.TwoLevelCache}.
     * @throws Exception
     */
    protected void setUp() throws Exception
    {
      ObjectCacheFactory.getInstance().setClassToServe(TwoLevelCache.class);
      //ObjectCacheFactory.getInstance().setClassToServe(org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl.class);
      super.setUp();
      old_CacheFilter = getConfig().getCacheFilters();
      old_ObjectCache = ObjectCacheFactory.getInstance().getClassToServe();
    }
  
    /**
     * undo Cache change.
     * @throws Exception
     */
    protected void tearDown() throws Exception
    {
      getConfig().setCacheFilters(old_CacheFilter);
      ObjectCacheFactory.getInstance().setClassToServe(old_ObjectCache);
      super.tearDown();
    }
  
    /**
     * retrieve one CdArticle twice.
     */
    public void testAccessArticleTwice()
    {
      PersistenceBroker pb0, pb1;
      pb0 = PersistenceBrokerFactory.defaultPersistenceBroker();
      pb1 = PersistenceBrokerFactory.defaultPersistenceBroker();
      assertNotSame(pb0, pb1);
  
      resetStmtCount();
      pb0.clearCache();
      pb0.beginTransaction();
      Identity id = new Identity(null, CloneableGroup.class, new Object[] {new Integer(1)});
      logger.info(id.toString());
      assertNull(id.getObjectsRealClass());
      Object group0 = pb0.getObjectByIdentity(id);
      assertNotNull(group0);
      assertEquals(CloneableGroup.class, id.getObjectsRealClass());
      assertStmtCount("access one group", 1);
      pb0.commitTransaction();
  
      resetStmtCount();
      pb1.beginTransaction();
      Object group1 = pb1.getObjectByIdentity(id);
      assertStmtCount("access one group again", 0); // lookup again, 2nd level hit, no SQL
access.
      assertNotSame(group0, group1);
      pb1.commitTransaction();
    }
  }
  
  
  
  1.1                  db-ojb/src/java/org/apache/ojb/broker/cache/TwoLevelCache.java
  
  Index: TwoLevelCache.java
  ===================================================================
  package org.apache.ojb.broker.cache;
  
  
  import org.apache.ojb.broker.Identity;
  import org.apache.ojb.broker.PBStateEvent;
  import org.apache.ojb.broker.PBStateListener;
  import org.apache.ojb.broker.PersistenceBroker;
  import org.apache.ojb.broker.VirtualProxy;
  import org.apache.ojb.broker.PBKey;
  import org.apache.ojb.broker.OJBRuntimeException;
  import org.apache.ojb.broker.util.logging.Logger;
  import org.apache.ojb.broker.util.logging.LoggerFactory;
  import org.apache.ojb.broker.core.PersistenceBrokerImpl;
  import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
  import org.apache.ojb.broker.metadata.ClassDescriptor;
  import org.apache.ojb.broker.metadata.DescriptorRepository;
  import org.apache.ojb.broker.metadata.CollectionDescriptor;
  import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
  import org.apache.ojb.broker.cache.ObjectCache;
  import org.apache.ojb.broker.cache.ObjectCacheDefaultImpl;
  import org.apache.commons.lang.builder.ToStringBuilder;
  import org.apache.commons.lang.builder.ToStringStyle;
  
  import java.lang.ref.SoftReference;
  import java.lang.reflect.Method;
  import java.lang.reflect.InvocationTargetException;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Iterator;
  import java.util.Properties;
  
  
  /**
   * A cache that delegates to another cache in case of misses,
   * and maintains that second-level cache with clones.
   *
   * Work in progress!
   *
   * Known problems:
   * <ul>
   * <li> If there is a non-proxied collection field in the cached object, then
   * inserting the object causes an extra query to fill that field. :-( </li>
   * <li> All references of the cached object are substituted with
   * proxies during caching, even if they are declared non-proxied. </li>
   * <li> The code for instantiating proxy references has been copied
   * from other OJB source. </li>
   * <li> the second-level cache does not cache collections.  Conseqently,
   * iterating thru a collection still requires a database query.
   * </li>
   * <li>If an object's class does not have a clone method or does not
   * implement the Cloneable tag interface, then an attempt to use the cache
   * will result in an exception.  Alternatively, we might choose to silently
   * ignore that situation.
   * </li>
   * <li> the class uses a static map, i.e., one per VM.  This map can
   * be cleared only by the static method {@link #clearSecondLevel()},
   * thus the usage of this class will infiltrate the (test) code even
   * in case this cache is not used at all. </li>
   * </ul>
   *
   * @see org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl
   * @author oliverm
   * @version $Id: TwoLevelCache.java,v 1.1 2003/10/28 09:15:20 oliverm Exp $
   */
  public class TwoLevelCache implements ObjectCache, PBStateListener
  {
    /**
     * the hashtable holding all cached objects
     */
    protected Map objectTable = null;
    private static ObjectCache secondLevelCache = new ObjectCacheDefaultImpl(null, null);
    private int secondLevelFoundCount;
  
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    private DescriptorRepository descriptorRepository;
    private PBKey pbKey;
    private PersistenceBroker myBroker;
    private int hitCount = 0;
    private int failCount = 0;
    private int gcCount = 0;
  
    /**
     * OJB calls this constructor by reflection.
     * @param props ignored
     */
    public TwoLevelCache(PersistenceBroker broker, Properties props)
    {
      objectTable = new HashMap();
      descriptorRepository = broker.getDescriptorRepository();
      myBroker = broker;
      pbKey = broker.getPBKey();
      // add this cache as permanent listener
      broker.addListener(this, true);
      // todo: maybe make this configurable.
    }
  
    private PBKey getPbKey()
    {
      return pbKey;
    }
  
    /**
     * @return the content of the repository.xml or the like.
     */
    protected final DescriptorRepository getDescriptorRepository()
    {
      return descriptorRepository;
    }
  
    /**
     * Clear ObjectCache. I.e. remove all entries for classes and objects.
     * Do not clear the second-level cache
     */
    public void clear()
    {
      objectTable.clear();
    }
  
    /**
     * clear the second level cache, which is shared by all instances
     * in the same VM.
     */
    public static void clearSecondLevel()
    {
      secondLevelCache.clear();
    }
  
    /**
     * If this method is being called while retrieving the object from
     * database, then the references and collections of obj are filled.
     *
     * Problem: if this method is called during insert inside a transaction
     * that is later rolledback, then there are objects entered into the second-level
     * cache that should not be there.
     */
    public void cache(Identity oid, Object obj)
    {
      if (logger.isDebugEnabled()) logger.debug("Caching object with identity " + oid);
      if ((obj != null))
      {
        SoftReference ref = new SoftReference(obj);
        objectTable.put(oid, ref);
      }
    }
  
    /**
     * Insert a( clone of a)n Object into the 2nd level cache.
     * @param oid the object's indetity
     * @param obj the object itsself
     */
    private void insertInto2ndLevel(Identity oid, Object obj)
    {
      // if we insert a clone rather than the object itsself into the second level cache,
      // this has the following consequence:
      // the collections and references are all null, not even proxies.
      // if we insert the object itsself, we have the following problem:
      // subsequent modfications to the object affect the cached version, too,
      // which might be unintended.
      try
      {
        Object clone = clone(obj);
        if (logger.isDebugEnabled())
          logger.debug("inserting clone into 2. level cache " + oid);
        unsetReferences(clone, obj);
        secondLevelCache.cache(oid, clone);
        if (logger.isDebugEnabled())
          logger.debug("cache insert: "  + myBroker + ": " + oid);
      }
      catch (CloneNotSupportedException exc)
      {
        logger.error("second level caching failed: " + exc);
      }
    }
  
    /**
     * Retrieve an object from the second level cache.
     * @param oid
     * @return a clone of the clone present in the cache, or null.
     */
    private Object lookupFrom2ndLevel(Identity oid)
    {
      Object cacheObj = secondLevelCache.lookup(oid);
      if (cacheObj != null)
      {
        // the object has been found in the cache.  All references and collections
        // should be null.  We have to the reference and collection fields
        try
        {
          Object obj = clone(cacheObj);
          initReferences(obj);
          initLookedUpCollections(obj, cacheObj);
          // We have to clone the object to preserve txn isolation.
          // Problem: the references and collections are identical in obj2 and its clone.
          // this spoils transaction isolation when these references are traversed.
          // todo: replace references and collections by suitable proxies or by clones.
          if (logger.isDebugEnabled()) logger.debug("+++ retrieved object from second level
cache.");
          objectTable.put(oid, new SoftReference(obj)); // insert into this broker's cache.
          secondLevelFoundCount++;
          return obj;
        }
        catch (CloneNotSupportedException exc)
        {
          logger.error("Cannot clone object from 2nd level cache: " + cacheObj);
        }
      }
      else
      {
          failCount++;
      }
      return null;
    }
  
    /**
     * code similar to PersistenceBrokerImpl
     */
    private Object getReferenceProxy(ClassDescriptor cld, ObjectReferenceDescriptor rds, Object
obj)
    {
      Object[] pkVals = rds.getForeignKeyValues(obj, cld);
      boolean allPkNull = true;
  
      for (int j = 0; j < pkVals.length; j++)
      {
        if (pkVals[j] != null)
        {
          allPkNull = false;
          break;
        }
      }
      if (allPkNull)
        return null;
      Class referencedProxy = rds.getItemProxyClass();
      if (referencedProxy == null)
      {
        referencedProxy = descriptorRepository.getDescriptorFor(rds.getItemClass()).getDynamicProxyClass();
      }
      return getProxy(rds.getItemClass(), referencedProxy, pkVals);
    }
  
    protected final Object getProxy(Class referencedClass, Class referencedProxy, Object[]
pkVals)
    {
      Class topLevelReferencedClass = descriptorRepository.getTopLevelClass(referencedClass);
      Object ret = VirtualProxy.createProxy(getPbKey(), referencedProxy,
              new Identity(null, topLevelReferencedClass, pkVals));
      return ret;
    }
  
    private Method getCloneMethod(Object in)
    {
      for (Class clazz = in.getClass(); ; )
      {
        try
        {
          return clazz.getDeclaredMethod("clone", null);
        }
        catch (NoSuchMethodException exc)
        {
          clazz = clazz.getSuperclass();
          if (clazz == null)
            throw new NoCloneMethod(in.getClass());
        }
      }
    }
  
    /**
     * this method is invoked when an object in inserted or retrieved from
     * the second level.
     *
     * Maybe overridden to do model-specific cloning or the like.
     *
     * @param in some object
     * @return clone of that object.
     * @throws CloneNotSupportedException
     */
    protected Object clone(Object in) throws CloneNotSupportedException
    {
      Method cloneMethod = getCloneMethod(in);
      try
      {
        return cloneMethod.invoke(in, null);
      }
      catch (IllegalAccessException exc)
      {
        throw new OJBRuntimeException(exc);
      }
      catch (InvocationTargetException exc)
      {
        throw new OJBRuntimeException(exc);
      }
    }
  
    /**
     * Initialize the non-embedded reference of obj (with suitable proxies.)
     * Make sure that all collection fields are proxies, otherwise queries are issued!!
     *
     * In the context where this method is called, obj had been clone after the basic type
fields
     * have been read from the database and before the references and collections have been
read.
     *
     * code similar to PersistenceBrokerImpl
     * This code could be moved into Object and could be replaced by code generation.
     */
    private void initReferences(Object obj)
    {
      if (logger.isDebugEnabled())
        logger.debug("initializing references");
      ClassDescriptor cld = descriptorRepository.getDescriptorFor(obj.getClass());
      for (Iterator i = cld.getObjectReferenceDescriptors().iterator(); i.hasNext(); )
      {
        ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
        Object refObj = getReferenceProxy(cld, rds, obj);
        PersistentField refField = rds.getPersistentField();
        if (logger.isDebugEnabled())
          logger.debug("setting reference: " + refField.getName() + ", " + (refObj == null
? ("<null>") : refObj.getClass().getName()));
        refField.set(obj, refObj);
      }
    }
  
    /**
     * Initializes the the non-embedded collection fields after the object has been retrieved
from the cache.
     * @param obj the freshly retrieved object
     * @param cacheObj its copy in the cache
     */
    protected void initLookedUpCollections(Object obj, Object cacheObj)
    {
      if (logger.isDebugEnabled())
        logger.debug("initializing collections");
      ClassDescriptor cld = descriptorRepository.getDescriptorFor(obj.getClass());
      // the next statements instantiates proxies only if the collection field is configured
      // accordingly.
      ((PersistenceBrokerImpl)myBroker).retrieveCollections(obj, cld, false);
    }
  
    /**
     * Set all non-embedded references and collections to null.
     * @param cacheObj
     */
    private void unsetReferences(Object cacheObj, Object origObj)
    {
      if (logger.isDebugEnabled())
        logger.debug("unsetting references");
      ClassDescriptor cld = descriptorRepository.getDescriptorFor(cacheObj.getClass());
      for (Iterator i = cld.getObjectReferenceDescriptors().iterator(); i.hasNext(); )
      {
        ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
        PersistentField refField = rds.getPersistentField();
        if (logger.isDebugEnabled())
          logger.debug("unsetting reference: " + refField.getName());
        refField.set(cacheObj, null);
      }
  
      for (Iterator i = cld.getCollectionDescriptors().iterator(); i.hasNext(); )
      {
        CollectionDescriptor colDesc = (CollectionDescriptor) i.next();
        if (logger.isDebugEnabled())
          logger.debug("unsetting collection: " + colDesc.getAttributeName());
        initCacheCollectionField(colDesc, cacheObj, origObj);
      }
    }
  
    /**
     * Sets the value of one collection field when an object is inserted into
     * the cache.
     *
     * @param colDesc
     * @param cacheObj the copy of the object in the cache
     * @param origObject the copy of the object in the scope of some PersistenceBroker
     */
    protected void initCacheCollectionField(CollectionDescriptor colDesc, Object cacheObj,
Object origObject)
    {
      PersistentField refField = colDesc.getPersistentField();
      refField.set(cacheObj, null);
    }
  
    /**
     * Does an extra lookup in the secondLevelCache if the first lookup fails.
     * @see org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl#lookup
     */
    public Object lookup(Identity oid)
    {
      if (logger.isDebugEnabled())
      {
        logger.debug("****** lookup " + myBroker + ": " + oid);
      }
      hitCount++;
  
      Object obj = null;
      SoftReference ref = (SoftReference) objectTable.get(oid);
      if (ref != null)
      {
        obj = ref.get();
        if (obj == null)
        {
          gcCount++;
          objectTable.remove(oid);    // Soft-referenced Object reclaimed by GC
        }
        else
        {
          if (logger.isDebugEnabled()) logger.debug("cache hit: "  + myBroker + ": " + oid);
        }
      }
      if (obj == null)
      {
        obj = lookupFrom2ndLevel(oid);
      }
      return obj;
    }
  
    public String toString()
    {
      ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
      buf.append("CACHE STATISTICS");
      buf.append("Count of cached objects", objectTable.keySet().size());
      buf.append("lookups", hitCount);
      buf.append("failures", failCount);
      buf.append("reclaimed", gcCount);
      buf.append("2. level success", secondLevelFoundCount);
      buf.append(secondLevelCache.toString());
      return buf.toString();
    }
  
    /**
     * Removes an Object from the cache.
     */
    public void remove(Identity oid)
    {
      if (oid != null)
      {
        objectTable.remove(oid);
        secondLevelCache.remove(oid);
      }
    }
  
    /**
     * We clear the cache
     */
    public void beforeClose(PBStateEvent event)
    {
      clear();
    }
  
    public void afterOpen(PBStateEvent event)
    {
    }
  
    public void beforeBegin(PBStateEvent event)
    {
    }
  
    public void afterBegin(PBStateEvent event)
    {
    }
  
    public void beforeCommit(PBStateEvent event)
    {
    }
  
    public void afterCommit(PBStateEvent event)
    {
      for (Iterator iter = this.objectTable.keySet().iterator(); iter.hasNext(); )
      {
        Identity oid = (Identity) iter.next();
        SoftReference ref = (SoftReference) objectTable.get(oid);
        Object obj = (Object) ref.get();
        if (obj != null)
        {
          insertInto2ndLevel(oid, obj);
        }
      }
      // clear();  // todo: decide whether to clear. oma.
    }
  
    public void beforeRollback(PBStateEvent event)
    {
      clear();
    }
  
    public void afterRollback(PBStateEvent event)
    {
      // clear to be in sync with DB
      // todo: avoid to throw away the second level cache.
      clear();
    }
  
    static class NoCloneMethod extends OJBRuntimeException
    {
      NoCloneMethod(Class clazz)
      {
        super(clazz.toString() + " has no clone method.");
      }
    }
  }
  
  
  
  1.19      +9 -7      db-ojb/src/test/org/apache/ojb/repository.xml
  
  Index: repository.xml
  ===================================================================
  RCS file: /home/cvs/db-ojb/src/test/org/apache/ojb/repository.xml,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- repository.xml	26 Oct 2003 00:03:21 -0000	1.18
  +++ repository.xml	28 Oct 2003 09:15:20 -0000	1.19
  @@ -1,5 +1,5 @@
   <?xml version="1.0" encoding="UTF-8"?>
  -<!-- This is a sample metadata repository for the 
  +<!-- This is a sample metadata repository for the
        Apache ObJectRelationalBridge (OJB) System.
        Use this file as a template for building your own mappings.
   -->
  @@ -9,13 +9,13 @@
        in the same directory as this repository.xml file.
        If you intend to validate your repository.xml against
        the public dtd at the Apache site, please replace the string
  -     "repository.dtd" 
  +     "repository.dtd"
        by the public adress
        "http://db.apache.org/ojb/dtds/1.0/repository.dtd".
        In this case validation will only work if the machine you
  -     run your application on can connect to the internet! 
  +     run your application on can connect to the internet!
   -->
  -     
  +
   <!DOCTYPE descriptor-repository PUBLIC
          "-//Apache Software Foundation//DTD OJB Repository//EN"
          "repository.dtd"
  @@ -23,13 +23,14 @@
   <!ENTITY database SYSTEM "repository_database.xml">
   <!ENTITY internal SYSTEM "repository_internal.xml">
   <!ENTITY junit SYSTEM "repository_junit.xml">
  +<!ENTITY junit_cloneable SYSTEM "repository_junit_cloneable.xml">
   <!ENTITY user SYSTEM "repository_user.xml">
   <!ENTITY ejb SYSTEM "repository_ejb.xml">
   <!ENTITY jdo SYSTEM "repository_jdo.xml">
   ]>
   
   
  -<descriptor-repository version="1.0" isolation-level="read-uncommitted" 
  +<descriptor-repository version="1.0" isolation-level="read-uncommitted"
           proxy-prefetching-limit="50">
   
       <!-- include all used database connections -->
  @@ -39,10 +40,11 @@
       &internal;
   
       <!-- include mappings for JUnit tests -->
  -    <!-- This could be removed (with <!ENTITY entry), 
  -         if junit test suite was not used 
  +    <!-- This could be removed (with <!ENTITY entry),
  +         if junit test suite was not used
       -->
       &junit;
  +    &junit_cloneable;
   
       <!-- include user defined mappings here -->
       &user;
  
  
  
  1.1                  db-ojb/src/test/org/apache/ojb/repository_junit_cloneable.xml
  
  Index: repository_junit_cloneable.xml
  ===================================================================
  
  <class-descriptor
     	  class="org.apache.ojb.broker.cloneable.CloneableGroup"
     	  proxy="dynamic"
     	  table="Kategorien"
  >
    <documentation>This class is used in the TwoLevelCache tests</documentation>
    <field-descriptor
           name="id"
           column="Kategorie_Nr"
           jdbc-type="INTEGER"
           primarykey="true"
           autoincrement="true"
    />
    <field-descriptor
       name="groupName"
       column="KategorieName"
       jdbc-type="VARCHAR"
    />
  </class-descriptor>
  
  
  1.1                  db-ojb/src/test/org/apache/ojb/broker/cloneable/CloneableGroup.java
  
  Index: CloneableGroup.java
  ===================================================================
  /*
   * (C) 2003 ppi Media
   * User: om
   */
  
  package org.apache.ojb.broker.cloneable;
  
  /**
   * implements Cloneable so that it can be used in TwoLevelCache
   *
   * @author <a href="mailto:om@ppi.de">Oliver Matz</a>
   * @version $Id: CloneableGroup.java,v 1.1 2003/10/28 09:15:20 oliverm Exp $
   */
  public class CloneableGroup implements Cloneable
  {
    private Integer id;
    private String groupName;
  
    /**
     * called by reflection in {@link org.apache.ojb.broker.cache.TwoLevelCache}.
     * @return
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException
    {
      return super.clone();
    }
  
    /**
     * mapped to column KategorieName
     */
    public final String getName()
    {
      return groupName;
    }
  
    public final void setName(String name)
    {
      this.groupName = name;
    }
  
    public String toString()
    {
      return "CloneableGroup#" + System.identityHashCode(this) + " - " + id + ": " + groupName;
    }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: ojb-dev-unsubscribe@db.apache.org
For additional commands, e-mail: ojb-dev-help@db.apache.org


Mime
View raw message