blob: b225f3f68d32f534f4629a7ad4c72f7a376e4e3e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.datacache;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.openjpa.enhance.PCDataGenerator;
import org.apache.openjpa.kernel.DataCacheRetrieveMode;
import org.apache.openjpa.kernel.DataCacheStoreMode;
import org.apache.openjpa.kernel.DelegatingStoreManager;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.LockLevels;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.PCState;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.kernel.StoreManager;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.util.OpenJPAId;
import org.apache.openjpa.util.OptimisticException;
/**
* StoreManager proxy that delegates to a data cache when possible.
*
* @author Patrick Linskey
*/
public class DataCacheStoreManager extends DelegatingStoreManager {
// all the state managers changed in this transaction
private Collection<OpenJPAStateManager> _inserts = null;
private Map<OpenJPAStateManager, BitSet> _updates = null;
private Collection<OpenJPAStateManager> _deletes = null;
// the owning context
private StoreContext _ctx = null;
private DataCacheManager _mgr = null;
// pc data generator
private PCDataGenerator _gen = null;
/**
* Constructor.
*
* @param sm the store manager to delegate to
*/
public DataCacheStoreManager(StoreManager sm) {
super(sm);
}
@Override
public void setContext(StoreContext ctx) {
_ctx = ctx;
_mgr = ctx.getConfiguration().getDataCacheManagerInstance();
_gen = _mgr.getPCDataGenerator();
super.setContext(ctx);
}
@Override
public void begin() {
super.begin();
}
@Override
public void commit() {
try {
super.commit();
updateCaches();
} finally {
_inserts = null;
_updates = null;
_deletes = null;
}
}
@Override
public void rollback() {
try {
super.rollback();
} finally {
_inserts = null;
_updates = null;
_deletes = null;
}
}
/**
* Evict all members of the given classes.
*/
private void evictTypes(Collection<Class<?>> classes) {
if (classes.isEmpty())
return;
MetaDataRepository mdr = _ctx.getConfiguration().getMetaDataRepositoryInstance();
ClassLoader loader = _ctx.getClassLoader();
DataCache cache;
for (Class<?> cls : classes) {
cache = mdr.getMetaData(cls, loader, false).getDataCache();
if (cache != null && cache.getEvictOnBulkUpdate())
cache.removeAll(cls, false);
}
}
/**
* Update all caches with the committed inserts, updates, and deletes.
*/
private void updateCaches() {
if(_ctx.getFetchConfiguration().getCacheStoreMode() != DataCacheStoreMode.BYPASS ) {
// map each data cache to the modifications we need to perform
Map<DataCache,Modifications> modMap = null;
if ((_ctx.getPopulateDataCache() && _inserts != null) || _updates != null || _deletes != null)
modMap = new HashMap<>();
Modifications mods;
DataCachePCData data;
DataCache cache;
// create pc datas for inserts
if (_ctx.getPopulateDataCache() && _inserts != null) {
for (OpenJPAStateManager sm : _inserts) {
cache = _mgr.selectCache(sm);
if (cache == null)
continue;
mods = getModifications(modMap, cache);
data = newPCData(sm, cache);
data.store(sm);
mods.additions.add(new PCDataHolder(data, sm));
CacheStatistics stats = cache.getStatistics();
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newPut(data.getType());
}
}
}
// update pcdatas for updates
if (_updates != null) {
BitSet fields;
OpenJPAStateManager sm;
for (Map.Entry<OpenJPAStateManager, BitSet> entry : _updates.entrySet()) {
sm = entry.getKey();
fields = entry.getValue();
cache = _mgr.selectCache(sm);
if (cache == null) {
continue;
}
// it's ok not to clone the object that we get from the cache,
// since we're inside the commit() method, so any modifications
// to the underlying cache are valid. If the commit had not
// already succeeded, then we'd want to clone the retrieved
// object.
data = cache.get(sm.getObjectId());
mods = getModifications(modMap, cache);
// data should always be non-null, since the object is
// dirty, but maybe it got dropped from the cache in the
// interim
if (data == null) {
data = newPCData(sm, cache);
data.store(sm);
mods.newUpdates.add(new PCDataHolder(data, sm));
} else {
data.store(sm, fields);
mods.existingUpdates.add(new PCDataHolder(data, sm));
}
CacheStatistics stats = cache.getStatistics();
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newPut(data.getType());
}
}
}
// remove pcdatas for deletes
if (_deletes != null) {
for (OpenJPAStateManager sm : _deletes) {
cache = _mgr.selectCache(sm);
if (cache == null)
continue;
mods = getModifications(modMap, cache);
mods.deletes.add(sm.getObjectId());
}
}
// notify the caches of the changes
if (modMap != null) {
for (Map.Entry<DataCache,Modifications> entry : modMap.entrySet()) {
cache = entry.getKey();
mods = entry.getValue();
// make sure we're not caching old versions
cache.writeLock();
try {
cache.commit(
transformToVersionSafePCDatas(cache, mods.additions),
transformToVersionSafePCDatas(cache, mods.newUpdates),
transformToVersionSafePCDatas(cache, mods.existingUpdates),
mods.deletes);
} finally {
cache.writeUnlock();
}
}
}
// if we were in largeTransaction mode, then we have recorded
// the classes of updated/deleted objects and these now need to be
// evicted
if (_ctx.isTrackChangesByType()) {
evictTypes(_ctx.getDeletedTypes());
evictTypes(_ctx.getUpdatedTypes());
}
}
}
/**
* Transforms a collection of {@link PCDataHolder}s that might contain
* stale instances into a collection of up-to-date {@link DataCachePCData}s.
*/
private List<DataCachePCData> transformToVersionSafePCDatas(DataCache cache, List<PCDataHolder> holders) {
List<DataCachePCData> transformed = new ArrayList<>(holders.size());
Map<Object,Integer> ids = new HashMap<>(holders.size());
// this list could be removed if DataCache.getAll() took a Collection
List<Object> idList = new ArrayList<>(holders.size());
int i = 0;
for (PCDataHolder holder : holders) {
ids.put(holder.sm.getObjectId(), i++);
idList.add(holder.sm.getObjectId());
}
Map<Object,DataCachePCData> pcdatas = cache.getAll(idList);
for (Entry<Object,DataCachePCData> entry : pcdatas.entrySet()) {
Integer index = ids.get(entry.getKey());
DataCachePCData oldpc = entry.getValue();
PCDataHolder holder = holders.get(index);
if (oldpc != null && compareVersion(holder.sm,
holder.sm.getVersion(), oldpc.getVersion()) == VERSION_EARLIER)
continue;
else
transformed.add(holder.pcdata);
}
return transformed;
}
/**
* Return a {@link Modifications} instance to track modifications
* to the given cache, creating and caching the instance if it does
* not already exist in the given map.
*/
private static Modifications getModifications(Map<DataCache,Modifications> modMap, DataCache cache) {
Modifications mods = (Modifications) modMap.get(cache);
if (mods == null) {
mods = new Modifications();
modMap.put(cache, mods);
}
return mods;
}
@Override
public boolean exists(OpenJPAStateManager sm, Object edata) {
DataCache cache = _mgr.selectCache(sm);
CacheStatistics stats = (cache == null) ? null : cache.getStatistics();
if (cache != null && !isLocking(null) && cache.contains(sm.getObjectId())){
if (stats != null && stats.isEnabled()) {
// delay this call ONLY if stats collection is enabled
Class<?> cls = sm.getMetaData().getDescribedType();
((CacheStatisticsSPI)stats).newGet(cls, false);
}
return true;
}
// If isLocking(null)==true && cache.contains(..) == true... probably shouldn't count?
if (stats != null && stats.isEnabled()) {
// delay this call ONLY if stats collection is enabled
Class<?> cls = sm.getMetaData().getDescribedType();
((CacheStatisticsSPI)stats).newGet(cls, false);
}
return super.exists(sm, edata);
}
@Override
public boolean isCached(List<Object> oids, BitSet edata) {
// If using partitioned cache, we were and still are broke.
DataCache cache = _mgr.getSystemDataCache();
if (cache != null && !isLocking(null)) {
// BitSet size is not consistent.
for(int i = 0; i < oids.size(); i++) {
Object oid = oids.get(i);
// Only check the cache if we haven't found the current oid.
if (!edata.get(i) && cache.contains(oid)) {
edata.set(i);
}
}
if (edata.cardinality() == oids.size()){
return true;
}
}
return super.isCached(oids, edata);
}
@Override
public boolean syncVersion(OpenJPAStateManager sm, Object edata) {
DataCache cache = _mgr.selectCache(sm);
FetchConfiguration fc = sm.getContext().getFetchConfiguration();
CacheStatistics stats = (cache == null) ? null : cache.getStatistics();
if (cache == null || sm.isEmbedded() || fc.getCacheRetrieveMode() == DataCacheRetrieveMode.BYPASS) {
if (stats != null && stats.isEnabled()) {
((CacheStatisticsSPI) stats).newGet(sm.getMetaData().getDescribedType(), false);
}
return super.syncVersion(sm, edata);
}
DataCachePCData data;
Object version = null;
data = cache.get(sm.getObjectId());
if (!isLocking(null) && data != null)
version = data.getVersion();
// if we have a cached version update from there
if (version != null) {
if (stats != null && stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(data.getType(), true);
}
if (!version.equals(sm.getVersion())) {
sm.setVersion(version);
return false;
}
return true;
}
if(stats.isEnabled()){
Class<?> cls = (data == null) ? sm.getMetaData().getDescribedType() : data.getType();
((CacheStatisticsSPI) stats).newGet(cls, false);
}
// use data store version
return super.syncVersion(sm, edata);
}
@Override
public boolean initialize(OpenJPAStateManager sm, PCState state, FetchConfiguration fetch, Object edata) {
DataCache cache = _mgr.selectCache(sm);
if (cache == null) {
return super.initialize(sm, state, fetch, edata);
}
DataCachePCData data = cache.get(sm.getObjectId());
CacheStatistics stats = cache.getStatistics();
boolean fromDatabase = false;
boolean alreadyCached = data != null;
if (sm.isEmbedded()
|| fetch.getCacheRetrieveMode() == DataCacheRetrieveMode.BYPASS
|| fetch.getCacheStoreMode() == DataCacheStoreMode.REFRESH) {
// stats -- Skipped reading from the cache, noop
fromDatabase = super.initialize(sm, state, fetch, edata);
} else {
if (alreadyCached && !isLocking(fetch)) {
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(data.getType(), true);
}
sm.initialize(data.getType(), state);
data.load(sm, fetch, edata);
} else {
if (!alreadyCached) {
if (stats.isEnabled()) {
// Get the classname from MetaData... but this won't be right in every case.
((CacheStatisticsSPI)stats).newGet(sm.getMetaData().getDescribedType(), false);
}
}
fromDatabase = super.initialize(sm, state, fetch, edata);
}
}
// update cache if the result came from the database and configured to use or refresh the cache.
boolean updateCache = fromDatabase && _ctx.getPopulateDataCache()
&& ((fetch.getCacheStoreMode() == DataCacheStoreMode.USE && !alreadyCached)
|| (fetch.getCacheStoreMode() == DataCacheStoreMode.REFRESH));
if (updateCache) {
// It is possible that the "cacheability" of the provided SM changed after hitting the DB. This can happen
// when we are operating against an Entity that is in some sort of inheritance structure.
cache = _mgr.selectCache(sm);
if (cache != null) {
cacheStateManager(cache, sm, data);
if (stats.isEnabled()) {
((CacheStatisticsSPI) stats).newPut(sm.getMetaData().getDescribedType());
}
}
}
return fromDatabase || alreadyCached;
}
private void cacheStateManager(DataCache cache, OpenJPAStateManager sm, DataCachePCData data) {
if (sm.isFlushed()) {
return;
}
// make sure that we're not trying to cache an old version
cache.writeLock();
try {
if (data != null && compareVersion(sm, sm.getVersion(), data.getVersion()) == VERSION_EARLIER) {
return;
}
// cache newly loaded info. It is safe to cache data frorm
// initialize() because this method is only called upon
// initial load of the data.
boolean isNew = data == null;
if (isNew) {
data = newPCData(sm, cache);
}
data.store(sm);
if (isNew) {
cache.put(data);
} else {
cache.update(data);
}
} finally {
cache.writeUnlock();
}
}
@Override
public boolean load(OpenJPAStateManager sm, BitSet fields,
FetchConfiguration fetch, int lockLevel, Object edata) {
DataCache cache = _mgr.selectCache(sm);
boolean found = false;
int loadedFieldsBefore = sm.getLoaded().cardinality();
if (cache == null || sm.isEmbedded() || bypass(fetch, StoreManager.FORCE_LOAD_NONE)) {
found = super.load(sm, fields, fetch, lockLevel, edata);
int loadedFieldsAfter = sm.getLoaded().cardinality();
boolean changed = loadedFieldsAfter > loadedFieldsBefore;
updateDataCache(found, sm, fetch, changed);
return found;
}
CacheStatistics stats = cache.getStatistics();
DataCachePCData data = cache.get(sm.getObjectId());
if (lockLevel == LockLevels.LOCK_NONE && !isLocking(fetch) && data != null)
data.load(sm, fields, fetch, edata);
if (fields.length() == 0){
if (stats.isEnabled()) {
Class<?> cls = (data == null) ? sm.getMetaData().getDescribedType() : data.getType();
((CacheStatisticsSPI)stats).newGet(cls, true);
}
return true;
}
// load from store manager; clone the set of still-unloaded fields
// so that if the store manager decides to modify it it won't affect us
found = super.load(sm,(BitSet) fields.clone() , fetch, lockLevel, edata);
int loadedFieldsAfter = sm.getLoaded().cardinality();
boolean changed = loadedFieldsAfter > loadedFieldsBefore;
// Get new instance of cache after DB load since it may have changed
updateDataCache(found, sm, fetch, changed);
return found;
}
/**
* Updates or inserts and item into the data cache. If storeMode=USE and not in the cache,
* the item is inserted. If storeMode=REFRESH the item is inserted, updated, or if found=false,
* removed from the cache.
* @param found whether the entity was found by the store manager
* @param sm the state manager
* @param fetch fetch configuration
*/
private void updateDataCache(boolean found, OpenJPAStateManager sm, FetchConfiguration fetch,
boolean loadedFieldsChanged) {
if (!_ctx.getPopulateDataCache() || sm == null || sm.isEmbedded()
|| fetch.getCacheStoreMode() == DataCacheStoreMode.BYPASS) {
return;
}
DataCache cache = _mgr.selectCache(sm);
if (cache == null) {
return;
}
DataCachePCData data = cache.get(sm.getObjectId());
// If loadedFieldsChanged = true, we don't care that data was already stored as we should update it.
boolean alreadyCached = (data != null && !loadedFieldsChanged);
DataCacheStoreMode storeMode = fetch.getCacheStoreMode();
if ((storeMode == DataCacheStoreMode.USE && !alreadyCached) || storeMode == DataCacheStoreMode.REFRESH) {
// If not found in the DB and the item is in the cache, and not locking remove the item
if (!found && data != null && !isLocking(fetch)) {
cache.remove(sm.getObjectId());
return;
}
// Update or insert the item into the cache
if (found) {
cacheStateManager(cache, sm, data);
CacheStatistics stats = cache.getStatistics();
if (stats.isEnabled()) {
((CacheStatisticsSPI) stats).newPut(sm.getMetaData().getDescribedType());
}
}
}
}
@Override
public Collection<Object> loadAll(Collection<OpenJPAStateManager> sms, PCState state, int load,
FetchConfiguration fetch, Object edata) {
if (bypass(fetch, load)) {
return super.loadAll(sms, state, load, fetch, edata);
}
Map<OpenJPAStateManager, BitSet> unloaded = null;
List<OpenJPAStateManager> smList = null;
Map<DataCache,List<OpenJPAStateManager>> caches = new HashMap<>();
DataCache cache;
DataCachePCData data;
BitSet fields;
for (OpenJPAStateManager sm : sms) {
cache = _mgr.selectCache(sm);
if (cache == null || sm.isEmbedded()) {
unloaded = addUnloaded(sm, null, unloaded);
continue;
}
if (sm.getManagedInstance() == null
|| load != FORCE_LOAD_NONE
|| sm.getPCState() == PCState.HOLLOW) {
smList = caches.computeIfAbsent(cache, k -> new ArrayList<>());
smList.add(sm);
} else if (!cache.contains(sm.getObjectId()))
unloaded = addUnloaded(sm, null, unloaded);
}
for(Entry<DataCache,List<OpenJPAStateManager>> entry : caches.entrySet()){
cache = entry.getKey();
smList = entry.getValue();
List<Object> oidList = new ArrayList<>(smList.size());
for (OpenJPAStateManager sm : smList) {
oidList.add((OpenJPAId) sm.getObjectId());
}
Map<Object,DataCachePCData> dataMap = cache.getAll(oidList);
for (OpenJPAStateManager sm : smList) {
data = dataMap.get(sm.getObjectId());
CacheStatistics stats = cache.getStatistics();
if (sm.getManagedInstance() == null) {
if (data != null) {
//### the 'data.type' access here probably needs
//### to be addressed for bug 511
if (stats.isEnabled()) {
((CacheStatisticsSPI) stats).newGet(data.getType(), true);
}
sm.initialize(data.getType(), state);
data.load(sm, fetch, edata);
} else {
unloaded = addUnloaded(sm, null, unloaded);
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(sm.getMetaData().getDescribedType(), false);
}
}
} else if (load != FORCE_LOAD_NONE
|| sm.getPCState() == PCState.HOLLOW) {
data = cache.get(sm.getObjectId());
if (data != null) {
// load unloaded fields
fields = sm.getUnloaded(fetch);
data.load(sm, fields, fetch, edata);
if (fields.length() > 0){
unloaded = addUnloaded(sm, fields, unloaded);
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(data.getType(), false);
}
}else{
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(data.getType(), true);
}
}
} else{
unloaded = addUnloaded(sm, null, unloaded);
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newGet(sm.getMetaData().getDescribedType(), false);
}
}
}
}
}
if (unloaded == null)
return Collections.emptyList();
// load with delegate
Collection<Object> failed = super.loadAll(unloaded.keySet(), state, load, fetch, edata);
if (!_ctx.getPopulateDataCache())
return failed;
// for each loaded instance, merge loaded state into cached data
boolean isNew;
for(Map.Entry<OpenJPAStateManager, BitSet> entry : unloaded.entrySet()) {
OpenJPAStateManager sm = entry.getKey();
fields = entry.getValue();
cache = _mgr.selectCache(sm);
if (cache == null || sm.isEmbedded() || (failed != null
&& failed.contains(sm.getId())))
continue;
// make sure that we're not trying to cache an old version
cache.writeLock();
try {
data = cache.get(sm.getObjectId());
if (data != null && compareVersion(sm, sm.getVersion(),
data.getVersion()) == VERSION_EARLIER)
continue;
isNew = data == null;
if (isNew)
data = newPCData(sm, cache);
if (fields == null)
data.store(sm);
else
data.store(sm, fields);
if (isNew)
cache.put(data);
else
cache.update(data);
CacheStatistics stats = cache.getStatistics();
if (stats.isEnabled()) {
((CacheStatisticsSPI)stats).newPut(data.getType());
}
} finally {
cache.writeUnlock();
}
}
return failed;
}
/**
* Helper method to add an unloaded instance to the given map.
*/
private static Map<OpenJPAStateManager, BitSet> addUnloaded(OpenJPAStateManager sm, BitSet fields,
Map<OpenJPAStateManager, BitSet> unloaded) {
if (unloaded == null)
unloaded = new HashMap<>();
unloaded.put(sm, fields);
return unloaded;
}
@Override
public Collection<Exception> flush(Collection<OpenJPAStateManager> states) {
Collection<Exception> exceps = super.flush(states);
// if there were errors evict bad instances and don't record changes
if (!exceps.isEmpty()) {
for (Exception e : exceps) {
if (e instanceof OptimisticException)
notifyOptimisticLockFailure((OptimisticException) e);
}
return exceps;
}
// if large transaction mode don't record individual changes
if (_ctx.isTrackChangesByType())
return exceps;
for (OpenJPAStateManager sm : states) {
if (sm.getPCState() == PCState.PNEW && !sm.isFlushed()) {
if (_inserts == null) {
_inserts = new ArrayList<>();
}
_inserts.add(sm);
// may have been re-persisted
if (_deletes != null) {
_deletes.remove(sm);
}
} else if (_inserts != null
&& (sm.getPCState() == PCState.PNEWDELETED
|| sm.getPCState() == PCState.PNEWFLUSHEDDELETED)) {
_inserts.remove(sm);
}
else if (sm.getPCState() == PCState.PDIRTY) {
if (_updates == null) {
_updates = new HashMap<>();
}
_updates.put(sm, sm.getDirty());
} else if (sm.getPCState() == PCState.PDELETED) {
if (_deletes == null) {
_deletes = new HashSet<>();
}
_deletes.add(sm);
}
}
return Collections.emptyList();
}
/**
* Fire local staleness detection events from the cache the OID (if
* available) that resulted in an optimistic lock exception iff the
* version information in the cache matches the version information
* in the state manager for the failed instance. This means that we
* will evict data from the cache for records that should have
* successfully committed according to the data cache but
* did not. The only predictable reason that could cause this behavior
* is a concurrent out-of-band modification to the database that was not
* communicated to the cache. This logic makes OpenJPA's data cache
* somewhat tolerant of such behavior, in that the cache will be cleaned
* up as failures occur.
*/
private void notifyOptimisticLockFailure(OptimisticException e) {
Object o = e.getFailedObject();
OpenJPAStateManager sm = _ctx.getStateManager(o);
if (sm == null)
return;
Object oid = sm.getId();
boolean remove;
// this logic could be more efficient -- we could aggregate
// all the cache->oid changes, and then use DataCache.removeAll()
// and less write locks to do the mutation.
DataCache cache = _mgr.selectCache(sm);
if (cache == null)
return;
cache.writeLock();
try {
DataCachePCData data = cache.get(oid);
if (data == null)
return;
switch (compareVersion(sm, sm.getVersion(), data.getVersion())) {
case StoreManager.VERSION_LATER:
case StoreManager.VERSION_SAME:
// This tx's current version is later than or the same as
// the data cache version. In this case, the commit should
// have succeeded from the standpoint of the cache. Remove
// the instance from cache in the hopes that the cache is
// out of sync.
remove = true;
break;
case StoreManager.VERSION_EARLIER:
// This tx's current version is earlier than the data
// cache version. This is a normal optimistic lock failure.
// Do not clean up the cache; it probably already has the
// right values, and if not, it'll get cleaned up by a tx
// that fails in one of the other case statements.
remove = false;
break;
case StoreManager.VERSION_DIFFERENT:
// The version strategy for the failed object does not
// store enough information to optimize for expected
// failures. Clean up the cache.
remove = true;
break;
default:
// Unexpected return value. Remove to be future-proof.
remove = true;
break;
}
if (remove)
// remove directly instead of via the RemoteCommitListener
// since we have a write lock here already, so this is more
// efficient than read-locking and then write-locking later.
cache.remove(sm.getId());
} finally {
cache.writeUnlock();
}
// fire off a remote commit stalenesss detection event.
_ctx.getConfiguration().getRemoteCommitEventManager()
.fireLocalStaleNotification(oid);
}
/**
* Create a new cacheable instance for the given state manager.
*/
private DataCachePCData newPCData(OpenJPAStateManager sm, DataCache cache) {
ClassMetaData meta = sm.getMetaData();
if (_gen != null)
return (DataCachePCData) _gen.generatePCData(sm.getObjectId(), meta);
return new DataCachePCDataImpl(sm.fetchObjectId(), meta, cache.getName());
}
/**
* Affirms if a load operation must bypass the L2 cache.
* If lock is active, always bypass.
*
*/
boolean bypass(FetchConfiguration fetch, int load) {
// Order of checks are important
if (isLocking(fetch))
return true;
if (_ctx.getConfiguration().getRefreshFromDataCache())
return false;
if (fetch.getCacheRetrieveMode() == DataCacheRetrieveMode.BYPASS)
return true;
if (load == StoreManager.FORCE_LOAD_REFRESH)
return true;
return false;
}
/**
* Return whether the context is locking loaded data.
*/
private boolean isLocking(FetchConfiguration fetch) {
if (fetch == null)
fetch = _ctx.getFetchConfiguration();
return fetch.getReadLockLevel() > LockLevels.LOCK_NONE;
}
/**
* Structure used during the commit process to track cache modifications.
*/
private static class Modifications {
public final List<PCDataHolder> additions = new ArrayList<>();
public final List<PCDataHolder> newUpdates = new ArrayList<>();
public final List<PCDataHolder> existingUpdates = new ArrayList<>();
public final List<Object> deletes = new ArrayList<>();
}
/**
* Utility structure holds the tuple of cacheable instance and its corresponding state manager.
*
*/
private static class PCDataHolder {
public final DataCachePCData pcdata;
public final OpenJPAStateManager sm;
public PCDataHolder(DataCachePCData pcdata, OpenJPAStateManager sm) {
this.pcdata = pcdata;
this.sm = sm;
}
}
}