Hello,

As I was not satisfy with the implementation of the TwoLevelCache,
mainly because it was not isolating the object in use in a transaction
with others transactions so I've tried to write another one and now I
would like too share it with the community.
For resuming here are the behavior of this cache :
When you retrieve an object a flat copy are made and the added in the
first level cache (a kind of broker session cache).
A call to store() will not immediately put the objet in the second
level cache but only in the first level (so this way other
transactions are isolated from update on the objet since your
transaction could finally abort)
Rollbacking a transaction simply clear the first level cache (the
broker session cache)
Commiting the transaction transfert the object from the first level
cache to the second level cache so that object update become visible
to other transactions.

There is 3 java files :

AbstractTwoLevelObjectCache.java
LocalTwoLevelObjectCache.java
CopyHelper.java (you should use the last update for commons-bean for this one)
IFlatClone.java

Just declare in OJB the LocalTwoLevelObjectCache for your new cache handler.

Here the code (sorry but it's not possible to attach file in the ojb
user group) :




package com.mycompagny.fwk.cache;


import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.builder.HashCodeBuilder;
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.OJBRuntimeException;
import org.apache.ojb.broker.cache.ObjectCache;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;


/**
 * 
 * 
 * This abstract class implements the behavior of the session cache
(first level cache)
 * and declare the abstract methods for implementing the application
cache (second level cache).
 * 
 * 
 *
 */
public abstract class AbstractTwoLevelObjectCache implements
ObjectCache, PBStateListener
{
        protected static final Logger LOG =
LoggerFactory.getLogger(AbstractTwoLevelObjectCache.class);
        
    public static final String CACHING_KEY_TYPE_PROP = "cachingKeyType";

    protected   PersistenceBroker       _broker;
    private     Map                             _sessionCache;
    
    /**
     * Determines how the key was build for the cached objects:
     * <br/>
     * 0 - Identity object was used as key
     * 1 - Idenity + jcdAlias name was used as key
     * 2 - Identity + model (DescriptorRepository) was used as key
     * 3 - all together (1+2)
     */
    private int cachingKeyType;


    public AbstractTwoLevelObjectCache(PersistenceBroker broker,
Properties prop)
    {
        this._broker = broker;
 
        cachingKeyType = prop == null ? 0 :
(Integer.parseInt(prop.getProperty(CACHING_KEY_TYPE_PROP, "0")));

        if (broker != null)
        {
            // we add this instance as a permanent PBStateListener
            broker.addListener(this, true);
        }

        _sessionCache = new Hashtable();
    }

    /**
     * Clear the session cache(first level cache) and the application
cache (second level cache)
     */
    public void clear()
    {
        clearApplicationCache();
        _sessionCache.clear();
    }



        /**
     * Makes object persistent to the sessionCache.
     * even if they are still cached.
     */
    public void cache(Identity oid, Object obj)
    {        
        if ((obj != null))
        {
            addToSessionCache(oid, obj);
        }
    }

    /**
     * Lookup object with Identity oid in : 
     * sessionCache, if not found in sessionCache the lookup the
applicationCache.
     * Returns null if no matching id is found
     */
    public Object lookup(Identity oid)
    {
        LOG.debug("lookup()");
        Object result = null;
        CacheEntry entry = null;
        
        // first lookup if the object is available in the application cache 
        result = lookupSessionCache(oid);
        if (result!=null)
                return result;
        
        // If not then lookup in the application cache :
        entry = lookUpApplicationCache(oid);
                        
        if (entry != null &&
entry.getOid().getObjectsTopLevelClass().equals(oid.getObjectsTopLevelClass()))
        {
                LOG.debug("Object found in Application Cache");

            Object objectFromApplicationCache = entry.getObject();
            
            if (objectFromApplicationCache == null)
            {
                remove(oid);
                // make sure that we return null
                result = null;
            }
            else
            {                
                // Make a copy WITHOUT any reference to other object 
                // (just copy the java type : have a look at
CopyHelper.copyJavaTypeProperties()
                // for more information)
                // Implementing IFlatClone is for optimization
consideration as it's a really faster method !
                if (entry.getObject() instanceof IFlatClone)
                        result = ((IFlatClone)entry.getObject()).flatClone();
                else 
                        result = 
CopyHelper.copyJavaTypeProperties(entry.getObject());
                LOG.debug("Making a copy and adding it to the session cache");
                addToSessionCache(oid, result);                
            }
        }
        else
        {
                LOG.debug("object NOT found in Application Cache");
        }
        return result;
    }

    

        /**
         * lookup in the session cache.
         * @param oid
         * @return
         */
        public Object lookupSessionCache(Identity oid)
    {
        LOG.debug("lookupSessionCache()");
        Object result = null;
        CacheEntry entry = null;
                
        entry = (CacheEntry) _sessionCache.get(buildKey(oid));
        
        if (entry != null &&
entry.getOid().getObjectsTopLevelClass().equals(oid.getObjectsTopLevelClass()))
        {
                LOG.debug("Object found in Session Cache");
  
                result = entry.getObject();
            
            if (result == null)
            {
                remove(oid);
                // make sure that we return null
                result = null;
            }
        }
        else
        {
                LOG.debug("object NOT found in Session Cache");
        }
        return result;
    }    
    
    
    /**
     * Removes an Object from the session cache and the application cache.
     */
    public void remove(Identity oid)
    {
        LOG.debug("remove()");
        if (oid != null)
        {            
            _sessionCache.remove(buildKey(oid));
            removeFromApplicationCache(oid);            
        }
    }

 

    /**
     * Add an objet in the session cache
     * @param oid
     * @param o
     */
    private void addToSessionCache(Identity oid, Object o)
    {
        LOG.debug("addToSessionCache()");
        if ((_broker != null) && _broker.isInTransaction())
        {
                LOG.debug("putting object to session cache : " + o);
            _sessionCache.put(buildKey(oid), new CacheEntry(o, oid));
        }
    }


    protected Integer buildKey(Identity oid)
    {
        int key = 0;
        switch(cachingKeyType)
        {
            case 0:
                key = oid.hashCode();
                break;
            case 1:
                key = new
HashCodeBuilder().append(_broker.getPBKey().getAlias().hashCode()).append(oid.hashCode()).toHashCode();
                break;
            case 2:
                key = new
HashCodeBuilder().append(_broker.getDescriptorRepository().hashCode()).append(oid.hashCode()).toHashCode();
                break;
            case 3:
                key = new
HashCodeBuilder().append(_broker.getDescriptorRepository().hashCode()).append(_broker.getPBKey().getAlias().hashCode()).append(oid.hashCode()).toHashCode();
                break;
            default:
                throw new OJBRuntimeException("Unexpected error,
'cacheType ="+cachingKeyType+"' was not supported");
        }
        return new Integer(key);
    }

        public String toString()
    {
        ToStringBuilder buf = new ToStringBuilder(this,
ToStringStyle.DEFAULT_STYLE);
        buf.append("Count of Session objects", _sessionCache.keySet().size());
        return buf.toString();
    }

    protected void finalize()
    {
        LoggerFactory.getDefaultLogger().debug("Finalize ObjectCache:
" + this.toString());
    }      
    
    
    //-----------------------------------------------------------
    // PBStateListener methods
    //-----------------------------------------------------------
        
    /**
     * Before rollbacking clear the session cache (first level cache)
     */
    public void beforeRollback(PBStateEvent event)
    {
        if (LOG.isDebugEnabled())
                LOG.debug("beforeRollback()");
        _sessionCache.clear();
    }

    /**
     * Before committing the transaction transfert (not copy) the objet 
     * from session cache ( 1st level cache) to the application cache
     * (2d level cache). Finally, clear the session cache.
     */
    public void beforeCommit(PBStateEvent event)
    {           
        if (LOG.isDebugEnabled())
                LOG.debug("beforeCommit()");
        
        for (Iterator iter = _sessionCache.entrySet().iterator();
iter.hasNext(); ) {
                Map.Entry entry = (Entry)iter.next();
                if (LOG.isDebugEnabled())
                        LOG.debug("Session Cache -> Application Cache : " +
((CacheEntry)entry.getValue()).getObject());
                
                addToApplicationCache(((CacheEntry)entry.getValue()).getOid(),
((CacheEntry)entry.getValue()).getObject());
        }       
        _sessionCache.clear();
    }

        
    /**
     * Before closing the PersistenceBroker insure that the session
cache is cleared
     */
        public void beforeClose(PBStateEvent event)
    {
                if (LOG.isDebugEnabled())
                        LOG.debug("Clearing the session cache");
        _sessionCache.clear();
    }

    public void afterRollback(PBStateEvent event) {}
    public void afterCommit(PBStateEvent event) {}
    public void afterBegin(PBStateEvent event) {}
    public void beforeBegin(PBStateEvent event) {}
    public void afterOpen(PBStateEvent event) {}



    //-----------------------------------------------------------
    // abstract methods
    //-----------------------------------------------------------
    
    /**
         * Clear the application cache (second level cache)
         */
        protected abstract void clearApplicationCache();

    /**
     * Add an object to the application cache (second level cache)
         * @param key
         * @param object
         */
        protected abstract void addToApplicationCache(Identity oid, Object 
object) ;
            
        /**
         * Remove an object from the application cache
         * @param oid
         */
        protected abstract void removeFromApplicationCache(Identity oid);

    /**
     * Lookup for an object in the Application cache
         * @param oid
         * @return
         */
        protected abstract CacheEntry lookUpApplicationCache(Identity oid);     
        
        
    //-----------------------------------------------------------
    // inner class
    //-----------------------------------------------------------
    public class CacheEntry
    {

                /**
                 * 
                 * @uml.property name="_oid" 
                 */
                private final Identity _oid;

                /**
                 * 
                 * @uml.property name="_object" 
                 */
                private final Object _object;

        
        public CacheEntry(Object object, final Identity k)
        {
                _object = object;
            _oid = k;
        }

                /**
                 * @return Returns the object.
                 * 
                 * @uml.property name="_object"
                 */
                public Object getObject() {
                        return _object;
                }

                /**
                 * @return Returns the oid.
                 * 
                 * @uml.property name="_oid"
                 */
                public Identity getOid() {
                        return _oid;
                }

    }

}



*************************************

/*
 * Created on 7 d�c. 2004
 *
 */
package com.mycompagny.fwk.cache;

import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;

import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;


/**
 * 
 * This class implements the abstract methods declared in
AbstractTwoLevelObjectCache.
 * It's implement the application cache (second level cache) 
 *
 */
public class LocalTwoLevelObjectCache extends AbstractTwoLevelObjectCache {
        protected static final Logger LOG =
LoggerFactory.getLogger(LocalTwoLevelObjectCache.class);
        
        protected static Map applicationCache = new Hashtable();        
        
        /**
         * @param broker
         * @param prop
         */
        public LocalTwoLevelObjectCache(PersistenceBroker broker, Properties 
prop) {
                super(broker, prop);
        }
        
        /* (non-Javadoc)
         * @see 
com.inetpsa.fwk.cache.AbstractTwoLevelObjectCache#lookUpApplicationCache(org.apache.ojb.broker.Identity)
         */
        protected CacheEntry lookUpApplicationCache(Identity oid) {
                LOG.debug("lookUpApplicationCache()");       
        CacheEntry entry = null;               
        entry = (CacheEntry) applicationCache.get(buildKey(oid));
        
        return entry;
        }

        /* (non-Javadoc)
         * @see 
com.inetpsa.fwk.cache.AbstractTwoLevelObjectCache#removeFromApplicationCache(org.apache.ojb.broker.Identity)
         */
        protected void removeFromApplicationCache(Identity oid) {
                applicationCache.remove(buildKey(oid));
        }

        /* (non-Javadoc)
         * @see 
com.inetpsa.fwk.cache.AbstractTwoLevelObjectCache#addToApplicationCache(java.lang.Object,
java.lang.Object)
         */
        protected void addToApplicationCache(Identity oid, Object object) {
                applicationCache.put(buildKey(oid), new
AbstractTwoLevelObjectCache.CacheEntry(object, oid));
        }

        /* (non-Javadoc)
         * @see 
com.inetpsa.fwk.cache.AbstractTwoLevelObjectCache#clearApplicationCache()
         */
        protected void clearApplicationCache() {
                applicationCache.clear();
        }

        
        public Map getApplicationCache() {
                return applicationCache;
        }
}


***********************************


package com.mycompagny.fwk.cache;

/**
 *
 */
public interface IFlatClone {
        Object flatClone();
}




*****************************


package com.mycompagny.fwk.cache;


import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.PropertyUtils;


/**
 * @author e232093
 *
 */
public class CopyHelper {

        private final static String JAVA_TYPE[] = {     "class java.lang.Long", 
                                                                                
                "long",
                                                                                
                "class java.lang.Integer",
                                                                                
                "int",
                                                                                
                "class java.lang.Double",
                                                                                
                "double",
                                                                                
                "class java.lang.Float",
                                                                                
                "float",
                                                                                
                "class java.lang.String",
                                                                                
                "char",
                                                                                
                "class java.lang.Boolean",
                                                                                
                "boolean"};

                                                                                
                
        
        public static Object copyJavaTypeProperties(Object pSource) {
                Object result = result = getNewInstance(pSource);
                PropertyDescriptor propDesc[] = 
PropertyUtils.getPropertyDescriptors(pSource);
                for (int i=0; i<propDesc.length;i++) {
                        String type = propDesc[i].getPropertyType().toString();
                        String name = propDesc[i].getDisplayName();
                        boolean writeable = PropertyUtils.isWriteable(pSource, 
name);
                        
                        try {
                                if (writeable && isTypeJavaSimple(type)) {
                                        Object value = 
PropertyUtils.getSimpleProperty(pSource, name);                                 
 
                                        PropertyUtils.setSimpleProperty(result, 
name, value);
                                }
                        } catch (IllegalAccessException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        } catch (InvocationTargetException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }                       
                }
                return result;
        }
        
        /**
         * Retourne une nouvelle instance du m�me type port� par obj.
         * @param obj
         * @return
         */
        private static Object getNewInstance(Object obj) {
                Object o = null;
                try {
                        o =  obj.getClass().newInstance();
                } catch (InstantiationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IllegalAccessException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
                return o;
        }       
        
        
        private static boolean isTypeJavaSimple(String pType) {
                for(int i=0; i<JAVA_TYPE.length; i++) {
                        if (pType.equalsIgnoreCase(JAVA_TYPE[i]))
                                return true;
                }
                return false;
        }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to