User: dsundstrom Date: 02/01/15 13:28:49 Added: src/main/org/jboss/ejb/plugins/cmp/jdbc ReadAheadCache.java Log: New readahead cahce. This cache uses the generic data containers in JDBCStoreManager, and supports readahead lists and preload data. Revision Changes Path 1.1 jboss/src/main/org/jboss/ejb/plugins/cmp/jdbc/ReadAheadCache.java Index: ReadAheadCache.java =================================================================== /* * JBoss, the OpenSource J2EE webOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.ejb.plugins.cmp.jdbc; import java.lang.ref.SoftReference; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.jboss.ejb.EntityEnterpriseContext; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge; import org.jboss.logging.Logger; import org.jboss.util.FinderResults; import org.jboss.util.LRUCachePolicy; /** * ReadAheadCache stores all of the data readahead for an entity. * Data is stored in the JDBCStoreManager entity tx data map on a per entity * basis. The read ahead data for each entity is stored with a soft reference. * * @author <a href="mailto:[EMAIL PROTECTED]">Dain Sundstrom</a> * @version $Revision: 1.1 $ */ public class ReadAheadCache { /** * To simplify null values handling in the preloaded data pool we use * this value instead of 'null' */ private static final Object NULL_VALUE = new Object(); private final JDBCStoreManager manager; private final Logger log; private Map listMap; private ListCache listCache; public ReadAheadCache(JDBCStoreManager manager) { this.manager = manager; // Create the Log log = Logger.getLogger( this.getClass().getName() + "." + manager.getMetaData().getName()); } public void create() { // Create the list map listMap = new HashMap(); // Create the list cache int listCacheMax = manager.getEntityBridge().getListCacheMax(); listCache = new ListCache(listCacheMax); listCache.create(); } public void start() { listCache.start(); } public void stop() { listMap.clear(); listCache.stop(); } public void destroy() { listCache.destroy(); listCache = null; listMap = null; } public synchronized void addFinderResult(FinderResults finderResults) { if(finderResults.size() <= 1) { // only cache results with more then one entry return; } if(!(finderResults.getAllKeys() instanceof List)) { log.warn("FinderResults does not contain a List. Read ahead will be " + "disabled for this query"); return; } // add the finder to the LRU list listCache.add(finderResults); // // Create a map between the entity prumary keys and the list. // The primary key will point to the last list added that contained the // primary key. // HashSet dereferencedResults = new HashSet(); Iterator iter = finderResults.iterator(); for(int i=0; iter.hasNext(); i++) { Object primaryKey = iter.next(); // Keep track of the resutls that have been dereferenced. Later we // all results from the list cache that are no longer referenced. EntityMapEntry oldInfo = (EntityMapEntry)listMap.put( primaryKey, new EntityMapEntry(i, finderResults)); if(oldInfo != null) { dereferencedResults.add(oldInfo.finderResults); } } // // Now we remove all lists from the list cache that are no longer // referenced in the list map. // // if we don't have any dereferenced results at this point we are done if(dereferencedResults.isEmpty()) { return; } // remove all lists from the dereferenced set that are still referenced // in the listMap iter = listMap.values().iterator(); while(iter.hasNext()) { EntityMapEntry entry = (EntityMapEntry)iter.next(); dereferencedResults.remove(entry.finderResults); } // if we don't have any dereferenced results at this point we are done if(dereferencedResults.isEmpty()) { return; } // remove all results from the cache that are no longer referenced iter = dereferencedResults.iterator(); while(iter.hasNext()) { Object obj = iter.next(); log.debug("Removing dereferenced finder results: "+obj); listCache.remove(obj); } } public synchronized void removeFinderResult(FinderResults finderResults) { // remove the list from the list cache listCache.remove(finderResults); // remove all primary keys from the listMap that reference this list if(listMap != null && !listMap.isEmpty()) { Iterator iter = listMap.values().iterator(); while(iter.hasNext()) { EntityMapEntry entry = (EntityMapEntry)iter.next(); if(entry.finderResults.equals(finderResults)) { iter.remove(); } } } } public synchronized EntityReadAheadInfo getEntityReadAheadInfo(Object pk) { EntityMapEntry entry = (EntityMapEntry)listMap.get(pk); // we're using this finder results so promote it to the head of the // LRU list if(entry != null) { listCache.promote(entry.finderResults); int pageSize = 1000; int from = entry.index; int to = Math.min(entry.finderResults.size(), entry.index + pageSize); List loadKeys = ((List)entry.finderResults.getAllKeys()).subList(from, to); return new EntityReadAheadInfo(loadKeys); } else { return new EntityReadAheadInfo(Collections.singletonList(pk)); } } /** * Loads all of the preloaded data for the ctx into it. * @param ctx the context that will be loaded */ public void load(EntityEnterpriseContext ctx) { log.debug("load data:" + " entity="+manager.getEntityBridge().getEntityName()+ " pk="+ctx.getId()); // get the preload data map Map preloadDataMap = getPreloadDataMap(ctx.getId(), false); if(preloadDataMap == null) { // no preloaded data for this entity log.debug("No preload data found:"+ " entity="+manager.getEntityBridge().getEntityName()+ " pk="+ctx.getId()); return; } // iterate over the keys in the preloaded map Iterator iter = preloadDataMap.keySet().iterator(); while(iter.hasNext()) { Object field = iter.next(); // get the value that was preloaded for this field Object value = preloadDataMap.get(field); // if we didn't get a value something is seriously hosed if(value == null) { throw new IllegalStateException("Preloaded value not found"); } // remove this value from the preload cache as it is about to be loaded iter.remove(); // check for null value standin if(value == NULL_VALUE) { value = null; } if(field instanceof JDBCCMPFieldBridge) { JDBCCMPFieldBridge cmpField = (JDBCCMPFieldBridge)field; if(!cmpField.isLoaded(ctx)) { // set the value cmpField.setInstanceValue(ctx, value); // mark this field clean as it's value was just loaded cmpField.setClean(ctx); } } else if(field instanceof JDBCCMRFieldBridge) { JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge)field; if(!cmrField.isLoaded(ctx)) { log.debug("Preloading data:" + " entity="+manager.getEntityBridge().getEntityName()+ " pk="+ctx.getId()+ " cmrField="+cmrField.getFieldName()); // set the value cmrField.loadPreloadedValue(ctx, (List)value); // add the loaded list to the related entity's readahead cache JDBCStoreManager relatedManager = cmrField.getRelatedCMRField().getJDBCStoreManager(); ReadAheadCache relatedReadAheadCache = relatedManager.getReadAheadCache(); relatedReadAheadCache.addFinderResult(new FinderResults( (List)value, null, null, null)); } else { log.debug("CMRField already loaded:" + " entity="+manager.getEntityBridge().getEntityName()+ " pk="+ctx.getId()+ " cmrField="+cmrField.getFieldName()); } } } // remove all preload data map as all of the data has been loaded manager.removeEntityTxData(new PreloadKey(ctx.getId())); } /** * Add preloaded data for an entity within the scope of a transaction */ public void addPreloadData( Object entityPrimaryKey, JDBCFieldBridge field, Object fieldValue) { if(field instanceof JDBCCMRFieldBridge) { if(fieldValue == null) { fieldValue = Collections.EMPTY_LIST; } else if(!(fieldValue instanceof Collection)) { fieldValue = Collections.singletonList(fieldValue); } } log.debug("Add preload data:" + " entity="+manager.getEntityBridge().getEntityName()+ " pk="+entityPrimaryKey+ " field="+field.getFieldName()); // convert null values to a null value standing object if(fieldValue == null) { fieldValue = NULL_VALUE; } // store the preloaded data getPreloadDataMap(entityPrimaryKey, true).put(field, fieldValue); } public synchronized void removeCachedData(Object primaryKey) { log.debug("Removing cached data for "+primaryKey); // remove the preloaded data manager.removeEntityTxData(new PreloadKey(primaryKey)); EntityMapEntry oldInfo = (EntityMapEntry)listMap.remove(primaryKey); // if the entity didn't have readahead entry; return if(oldInfo == null) { return; } // check to see if the dereferenced finder result is still referenced Iterator iter = listMap.values().iterator(); while(iter.hasNext()) { EntityMapEntry entry = (EntityMapEntry)iter.next(); if(entry.finderResults.equals(oldInfo.finderResults)) { // ok it is still referenced return; } } // a reference to the old finder set was not found so remove it log.debug("Removing dereferenced finder results: "+oldInfo.finderResults); listCache.remove(oldInfo.finderResults); } /** * Gets the map of preloaded data. * @param entityPrimaryKey the primary key of the entity * @param create should a new preload data map be created if one is not found * @return the preload data map for null if one is not found */ private Map getPreloadDataMap(Object entityPrimaryKey, boolean create) { // // Be careful in this code. A soft reference may be cleared at any time, // so don't check if a reference has a value and then get that value. // Instead get the value and then check if it is null. // // create a preload key for the entity PreloadKey preloadKey = new PreloadKey(entityPrimaryKey); // get the soft reference to the preload data map SoftReference ref = (SoftReference)manager.getEntityTxData(preloadKey); // did we get a reference if(ref != null) { // get the map from the reference Map preloadDataMap = (Map)ref.get(); // did we actually get a map? (will be null if it has been GC'd) if(preloadDataMap != null) { return preloadDataMap; } } // // at this point we did not get an existing value // // if we got a dead reference remove it if(ref != null) { manager.removeEntityTxData(preloadKey); } // if we are not creating, we're done if(!create) { return null; } // create the new preload data map Map preloadDataMap = new HashMap(); // create new soft reference ref = new SoftReference(preloadDataMap); // store the reference manager.putEntityTxData(preloadKey, ref); // return the new preload data map return preloadDataMap; } private class ListCache extends LRUCachePolicy { public ListCache(int max) { super(2, max); } public void add(FinderResults r) { insert(r, r); } public void promote(FinderResults r) { get(r); } protected void ageOut(LRUCacheEntry entry) { removeFinderResult((FinderResults)entry.m_key); } } /** * Wraps an entity primary key, so it does not collide with other * data stored in the entityTxDataMap. */ private class PreloadKey { private final Object entityPrimaryKey; public PreloadKey(Object entityPrimaryKey) { if(entityPrimaryKey == null) { throw new IllegalArgumentException("Entity primary key is null"); } this.entityPrimaryKey = entityPrimaryKey; } public boolean equals(Object object) { if(object instanceof PreloadKey) { PreloadKey preloadKey = (PreloadKey)object; return preloadKey.entityPrimaryKey.equals(entityPrimaryKey); } return false; } public int hashCode() { return entityPrimaryKey.hashCode(); } public String toString() { return "PreloadKey: entityId="+entityPrimaryKey; } } private class EntityMapEntry { public final int index; public final FinderResults finderResults; private EntityMapEntry(int index, FinderResults finderResults) { this.index = index; this.finderResults = finderResults; } } public class EntityReadAheadInfo { private final List loadKeys; private EntityReadAheadInfo(List loadKeys) { this.loadKeys = loadKeys; } public List getLoadKeys() { return loadKeys; } } }
_______________________________________________ Jboss-development mailing list [EMAIL PROTECTED] https://lists.sourceforge.net/lists/listinfo/jboss-development
