package org.jboss.ejb.plugins.cmp.jdbc;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
import org.jboss.logging.Logger;
import org.jboss.tm.TransactionLocal;
import javax.transaction.Transaction;
import javax.transaction.SystemException;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public final class ReadAheadCache
{
private static final Object NULL_VALUE = new Object();
private final JDBCStoreManager manager;
private final Logger log;
private final TransactionLocal listMapTxLocal = new TransactionLocal()
{
protected Object initialValue()
{
return new HashMap();
}
public Transaction getTransaction()
{
try
{
return transactionManager.getTransaction();
}
catch(SystemException e)
{
throw new IllegalStateException("An error occured while getting the " +
"transaction associated with the current thread: " + e);
}
}
};
private ListCache listCache;
private int listCacheMax;
public ReadAheadCache(JDBCStoreManager manager)
{
this.manager = manager;
log = Logger.getLogger(
this.getClass().getName() +
"." +
manager.getMetaData().getName());
}
public void create()
{
listCacheMax = ((JDBCEntityBridge)manager.getEntityBridge()).getListCacheMax();
listCache = new ListCache(listCacheMax);
}
public void start()
{
}
public void stop()
{
listCache.clear();
}
public void destroy()
{
listCache = null;
}
public void addFinderResults(List results, JDBCReadAheadMetaData readahead)
{
if(listCacheMax == 0 || results.size() < 2)
{
return;
}
Map listMap = getListMap();
if(listMap == null)
{
return;
}
if(log.isTraceEnabled())
{
log.trace("Add finder results:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" results=" + results +
" readahead=" + readahead);
}
if(!readahead.isNone())
{
listCache.add(results);
}
HashSet dereferencedResults = new HashSet();
Iterator iter = results.iterator();
for(int i = 0; iter.hasNext(); i++)
{
Object pk = iter.next();
EntityMapEntry entry;
if(readahead.isNone())
{
entry = new EntityMapEntry(0, Collections.singletonList(pk), readahead);
}
else
{
entry = new EntityMapEntry(i, results, readahead);
}
EntityMapEntry oldInfo = (EntityMapEntry) listMap.put(pk, entry);
if(oldInfo != null)
{
dereferencedResults.add(oldInfo.results);
}
}
if(dereferencedResults.isEmpty())
{
return;
}
iter = dereferencedResults.iterator();
while(iter.hasNext())
{
List dereferencedList = (List) iter.next();
boolean listHasReference = false;
Iterator iter2 = dereferencedList.iterator();
while(!listHasReference &&
iter2.hasNext())
{
EntityMapEntry entry = (EntityMapEntry) listMap.get(iter2.next());
if(entry != null && entry.results == dereferencedList)
{
listHasReference = true;
}
}
if(listHasReference)
{
iter.remove();
}
}
if(dereferencedResults.isEmpty())
{
return;
}
iter = dereferencedResults.iterator();
while(iter.hasNext())
{
List list = (List) iter.next();
if(log.isTraceEnabled())
{
log.trace("Removing dereferenced results: " + list);
}
listCache.remove(list);
}
}
private void removeFinderResult(List results)
{
Map listMap = getListMap();
if(listMap == null)
{
return;
}
listCache.remove(results);
if(!listMap.isEmpty())
{
Iterator iter = listMap.values().iterator();
while(iter.hasNext())
{
EntityMapEntry entry = (EntityMapEntry) iter.next();
if(entry.results == results)
{
iter.remove();
}
}
}
}
public EntityReadAheadInfo getEntityReadAheadInfo(Object pk)
{
Map listMap = getListMap();
if(listMap == null)
{
return new EntityReadAheadInfo(Collections.singletonList(pk));
}
EntityMapEntry entry = (EntityMapEntry) getListMap().get(pk);
if(entry != null)
{
if(!entry.readahead.isNone())
{
listCache.promote(entry.results);
}
JDBCReadAheadMetaData readahead = entry.readahead;
if(readahead == null)
{
readahead = manager.getMetaData().getReadAhead();
}
int from = entry.index;
int to = Math.min(entry.results.size(), entry.index + readahead.getPageSize());
List loadKeys = entry.results.subList(from, to);
return new EntityReadAheadInfo(loadKeys, readahead);
}
else
{
return new EntityReadAheadInfo(Collections.singletonList(pk));
}
}
public boolean load(EntityEnterpriseContext ctx)
{
if(log.isTraceEnabled())
{
log.trace("load data:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId());
}
Map preloadDataMap = getPreloadDataMap(ctx.getId(), false);
if(preloadDataMap == null || preloadDataMap.isEmpty())
{
if(log.isTraceEnabled())
{
log.trace("No preload data found:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId());
}
return false;
}
boolean cleanReadAhead = manager.getMetaData().isCleanReadAheadOnLoad();
Iterator iter = preloadDataMap.entrySet().iterator();
while(iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
Object field = entry.getKey();
Object value = entry.getValue();
if(value == null)
{
throw new IllegalStateException("Preloaded value not found");
}
if(cleanReadAhead)
{
iter.remove();
}
if(value == NULL_VALUE)
{
value = null;
}
if(field instanceof JDBCCMPFieldBridge)
{
JDBCCMPFieldBridge cmpField = (JDBCCMPFieldBridge) field;
if(!cmpField.isLoaded(ctx))
{
if(log.isTraceEnabled())
{
log.trace("Preloading data:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId() +
" cmpField=" + cmpField.getFieldName());
}
cmpField.setInstanceValue(ctx, value);
cmpField.setClean(ctx);
}
else
{
if(log.isTraceEnabled())
{
log.trace("CMRField already loaded:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId() +
" cmpField=" + cmpField.getFieldName());
}
}
}
else if(field instanceof JDBCCMRFieldBridge)
{
JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) field;
if(!cmrField.isLoaded(ctx))
{
if(log.isTraceEnabled())
{
log.trace("Preloading data:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId() +
" cmrField=" + cmrField.getFieldName());
}
cmrField.load(ctx, (List) value);
JDBCStoreManager relatedManager = (JDBCStoreManager) cmrField.getRelatedCMRField().getManager();
ReadAheadCache relatedReadAheadCache =
relatedManager.getReadAheadCache();
relatedReadAheadCache.addFinderResults(
(List) value, cmrField.getReadAhead());
}
else
{
if(log.isTraceEnabled())
{
log.trace("CMRField already loaded:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + ctx.getId() +
" cmrField=" + cmrField.getFieldName());
}
}
}
}
if(cleanReadAhead)
{
manager.removeEntityTxData(new PreloadKey(ctx.getId()));
}
return true;
}
public Collection getCachedCMRValue(Object pk, JDBCCMRFieldBridge cmrField)
{
Map preloadDataMap = getPreloadDataMap(pk, true);
return (Collection)preloadDataMap.get(cmrField);
}
public void addPreloadData(Object pk,
JDBCFieldBridge field,
Object fieldValue)
{
if(field instanceof JDBCCMRFieldBridge)
{
if(fieldValue == null)
{
fieldValue = Collections.EMPTY_LIST;
}
else if(!(fieldValue instanceof Collection))
{
fieldValue = Collections.singletonList(fieldValue);
}
}
if(log.isTraceEnabled())
{
log.trace("Add preload data:" +
" entity=" + manager.getEntityBridge().getEntityName() +
" pk=" + pk +
" field=" + field.getFieldName());
}
if(fieldValue == null)
{
fieldValue = NULL_VALUE;
}
Map preloadDataMap = getPreloadDataMap(pk, true);
Object overriden = preloadDataMap.put(field, fieldValue);
if(log.isTraceEnabled() && overriden != null)
{
log.trace(
"Overriding cached value " + overriden +
" with " + (fieldValue == NULL_VALUE ? null : fieldValue) +
". pk=" + pk +
", field=" + field.getFieldName()
);
}
}
public void removeCachedData(Object primaryKey)
{
if(log.isTraceEnabled())
{
log.trace("Removing cached data for " + primaryKey);
}
Map listMap = getListMap();
if(listMap == null)
{
return;
}
manager.removeEntityTxData(new PreloadKey(primaryKey));
EntityMapEntry oldInfo = (EntityMapEntry) listMap.remove(primaryKey);
if(oldInfo == null || oldInfo.readahead.isNone())
{
return;
}
Iterator iter = listMap.values().iterator();
while(iter.hasNext())
{
EntityMapEntry entry = (EntityMapEntry) iter.next();
if(entry.results == oldInfo.results)
{
return;
}
}
if(log.isTraceEnabled())
{
log.trace("Removing dereferenced finder results: " +
oldInfo.results);
}
listCache.remove(oldInfo.results);
}
public Map getPreloadDataMap(Object entityPrimaryKey, boolean create)
{
PreloadKey preloadKey = new PreloadKey(entityPrimaryKey);
SoftReference ref = (SoftReference) manager.getEntityTxData(preloadKey);
if(ref != null)
{
Map preloadDataMap = (Map) ref.get();
if(preloadDataMap != null)
{
return preloadDataMap;
}
}
if(ref != null)
{
manager.removeEntityTxData(preloadKey);
}
if(!create)
{
return null;
}
Map preloadDataMap = new HashMap();
ref = new SoftReference(preloadDataMap);
manager.putEntityTxData(preloadKey, ref);
return preloadDataMap;
}
private Map getListMap()
{
return (Map) listMapTxLocal.get();
}
private final class ListCache
{
private final TransactionLocal cacheTxLocal = new TransactionLocal()
{
protected Object initialValue()
{
return new LinkedList();
}
public Transaction getTransaction()
{
try
{
return transactionManager.getTransaction();
}
catch(SystemException e)
{
throw new IllegalStateException("An error occured while getting the " +
"transaction associated with the current thread: " + e);
}
}
};
private int max;
public ListCache(int max)
{
if(max < 0)
throw new IllegalArgumentException("list-cache-max is negative: " + max);
this.max = max;
}
public void add(List list)
{
if(max == 0)
{
return;
}
LinkedList cache = getCache();
if(cache == null)
return;
cache.addFirst(new IdentityObject(list));
while(cache.size() > max)
{
IdentityObject object = (IdentityObject) cache.removeLast();
ageOut((List) object.getObject());
}
}
public void promote(List list)
{
if(max == 0)
{
return;
}
LinkedList cache = getCache();
if(cache == null)
return;
IdentityObject object = new IdentityObject(list);
if(cache.remove(object))
{
cache.addFirst(object);
}
}
public void remove(List list)
{
if(max == 0)
{
return;
}
LinkedList cache = getCache();
if(cache != null)
cache.remove(new IdentityObject(list));
}
public void clear()
{
if(max == 0)
{
return;
}
}
private void ageOut(List list)
{
removeFinderResult(list);
}
private LinkedList getCache()
{
return (LinkedList) cacheTxLocal.get();
}
}
private static final 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 static final class EntityMapEntry
{
public final int index;
public final List results;
public final JDBCReadAheadMetaData readahead;
private EntityMapEntry(
int index,
List results,
JDBCReadAheadMetaData readahead)
{
this.index = index;
this.results = results;
this.readahead = readahead;
}
}
public final static class EntityReadAheadInfo
{
private final List loadKeys;
private final JDBCReadAheadMetaData readahead;
private EntityReadAheadInfo(List loadKeys)
{
this(loadKeys, null);
}
private EntityReadAheadInfo(List loadKeys, JDBCReadAheadMetaData r)
{
this.loadKeys = loadKeys;
this.readahead = r;
}
public List getLoadKeys()
{
return loadKeys;
}
public JDBCReadAheadMetaData getReadAhead()
{
return readahead;
}
}
private static final class IdentityObject
{
private final Object object;
public IdentityObject(Object object)
{
if(object == null)
{
throw new IllegalArgumentException("Object is null");
}
this.object = object;
}
public Object getObject()
{
return object;
}
public boolean equals(Object object)
{
return this.object == object;
}
public int hashCode()
{
return object.hashCode();
}
public String toString()
{
return object.toString();
}
}
}