package er.extensions.eof;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.eoaccess.EODatabase;
import com.webobjects.eoaccess.EODatabaseContext;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFaultHandler;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOGlobalID;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import er.extensions.foundation.ERXExpiringCache;
/**
* Transparent cache for fetch results, uses ERXFetchSpecifictation.identifier() as a key.
* @author ak
*
*/
public class ERXFetchResultCache {
private static EODatabase currentDatabase;
private ERXExpiringCache<String, NSArray<EOGlobalID>> cache = new ERXExpiringCache<String, NSArray<EOGlobalID>>() {
@Override
protected synchronized void removeEntryForKey(Entry<NSArray<EOGlobalID>> entry, String key) {
for (EOGlobalID gid : entry.object()) {
currentDatabase.decrementSnapshotCountForGlobalID(gid);
}
super.removeEntryForKey(entry, key);
}
@Override
protected synchronized void setEntryForKey(Entry<NSArray<EOGlobalID>> entry, String key) {
super.setEntryForKey(entry, key);
for (EOGlobalID gid : entry.object()) {
currentDatabase.incrementSnapshotCountForGlobalID(gid);
}
}
};
private static final Logger log = LoggerFactory.getLogger(ERXFetchResultCache.class);
/**
* Returns an array of EOs that where cached for the given fetch specification or null.
* @param ec
* @param fs
*/
public NSArray<? extends EOEnterpriseObject> objectsForFetchSpecification(EODatabaseContext dbc, EOEditingContext ec, EOFetchSpecification fs) {
String identifier = ERXFetchSpecification.identifierForFetchSpec(fs);
synchronized (cache) {
currentDatabase = dbc.database();
NSArray<EOGlobalID> gids = cache.objectForKey(identifier);
NSArray result = null;
if(gids != null) {
NSMutableArray<EOEnterpriseObject> eos = new NSMutableArray<>(gids.count());
EODatabase database = dbc.database();
for (EOGlobalID gid : gids) {
NSDictionary snapshotForGlobalID = database.snapshotForGlobalID(gid);
if(snapshotForGlobalID == null || dbc.snapshotForGlobalID(gid, ec.fetchTimestamp()) == null) {
// not found with recent timestamp
return null;
}
database.recordSnapshotForGlobalID(snapshotForGlobalID, gid);
EOEnterpriseObject eo = ec.faultForGlobalID(gid, ec);
eos.addObject(eo);
}
result = eos;
}
currentDatabase = null;
if(log.isDebugEnabled()) {
boolean hit = result != null;
log.info("Cache: {} on {}", hit ? "HIT" : "MISS", fs.entityName());
}
return result;
}
}
/**
* Returns a list of entities that should not be cached.
*/
protected NSArray<String> excludedEntities() {
return NSArray.EmptyArray;
}
/**
* Returns the time the result should stay in the cache. Less or equal than zero means don't cache.
* @param fs
*/
protected long cacheTime(NSArray eos, EOFetchSpecification fs) {
if(fs.fetchesRawRows() || fs.refreshesRefetchedObjects()) {
return 0;
}
for (Object object : eos) {
if (!(object instanceof EOEnterpriseObject)) {
return 0;
}
if (EOFaultHandler.isFault(object)) {
return 0;
}
if (excludedEntities().containsObject(((EOEnterpriseObject)object).entityName())) {
return 0;
}
}
return 100L;
}
/**
* Registers eos for a given fetch spec.
* @param ec
* @param dbc
* @param eos
* @param fs
*/
public void setObjectsForFetchSpecification(EODatabaseContext dbc, EOEditingContext ec, NSArray<?> eos, EOFetchSpecification fs) {
String identifier = ERXFetchSpecification.identifierForFetchSpec(fs);
synchronized (cache) {
currentDatabase = dbc.database();
long cacheTime = cacheTime(eos, fs);
if(cacheTime > 0) {
NSArray<EOGlobalID> gids = ERXEOControlUtilities.globalIDsForObjects(eos);
cache.setObjectForKeyWithVersion(gids, identifier, null, cacheTime);
}
if(log.isDebugEnabled()) {
log.debug("Cache: {} on {}", cacheTime > 0 ? "SET" : "DROP", fs.entityName());
}
currentDatabase = null;
}
}
}