blob: c48a0420e2a195ea1e8580fd853cfcd0b13494ad [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.modules.hibernate.internal;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CollectionRegion;
import org.hibernate.cache.access.CollectionRegionAccessStrategy;
import org.hibernate.cache.access.SoftLock;
import org.hibernate.cache.entry.CollectionCacheEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gemstone.gemfire.cache.CacheWriterException;
import com.gemstone.gemfire.cache.EntryExistsException;
import com.gemstone.gemfire.cache.EntryNotFoundException;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.client.ServerOperationException;
public class CollectionAccess implements
CollectionRegionAccessStrategy {
private final GemFireCollectionRegion region;
private Logger log = LoggerFactory.getLogger(getClass());
/**
* if we know the entity whose ids are stored in this
* collection, we can prefetch those entities using
* getAll. This field stores that child entity name.
*/
private String childEntityName;
public CollectionAccess(GemFireCollectionRegion region) {
this.region = region;
String regionName = this.region.getGemFireRegion().getName().trim();
regionName = regionName.replace("\\/", "");
int lastPeriod = regionName.lastIndexOf('.');
if (lastPeriod < 0) {
log.info("Eager prefetching disabled for region: {}", this.region.getName());
return;
}
String entityName = regionName.substring(0, lastPeriod);
String collectionFieldName = regionName.substring(lastPeriod+1);
log.debug("entity name: {}, collectionFieldName: {}", entityName, collectionFieldName);
try {
Class parentClass = Class.forName(entityName);
Field[] fields = parentClass.getDeclaredFields();
for (Field field : fields) {
log.debug("genericType: {}", field.getGenericType());
if (field.getName().equals(collectionFieldName)) {
String genericString = field.toGenericString();
log.debug("genericType: for required field name: {}", field.toGenericString());
int startDependentEntityIndex = genericString.indexOf("<");
if (startDependentEntityIndex != -1 &&
genericString.indexOf("<", startDependentEntityIndex+1) == -1) {
int childDependentEntityIndex = genericString.indexOf(">");
this.childEntityName = genericString.substring(startDependentEntityIndex+1, childDependentEntityIndex);
log.debug("For Collection {} using child entity: {}", this.region.getGemFireRegion().getName(), this.childEntityName);
}
}
}
}
catch (ClassNotFoundException e) {
//ok to ignore, we will not use pre-fetching
}
if (this.childEntityName == null) {
log.info("Eager prefetching disabled for region: {}", this.region.getName());
}
}
@Override
public CollectionRegion getRegion() {
return this.region;
}
@Override
public Object get(Object key, long txTimestamp) throws CacheException {
EntityWrapper wrapper = this.region.getGemFireRegion().get(key);
if (wrapper == null) {
this.region.getStats().incCacheMiss();
log.debug("Cache miss for {} ts: {}",key, txTimestamp);
return null;
} else {
this.region.getStats().incCacheHit();
log.debug("cache hit {} count: {} ", key, this.region.getStats().getCacheHits());
// do pre-fetching
if (isPrefetchPossible()) {
log.debug("for key: {} prefetching entries: {}", key, wrapper.getEntity());
prefetchKeys((CollectionCacheEntry)wrapper.getEntity());
}
}
return wrapper.getEntity();
}
private void prefetchKeys(CollectionCacheEntry entry) {
StringBuilder builder = new StringBuilder(this.childEntityName+"#");
Serializable[] childEntityKeys = entry.getState();
Set<String> getAllSet = new HashSet<String>();
for (Serializable id : childEntityKeys) {
String key = builder.append(id).toString();
log.debug("adding key {} to getAll set", key);
getAllSet.add(key);
}
GemFireEntityRegion childRegion = this.region.regionFactory.getEntityRegion(this.childEntityName);
log.debug("prefetching {} keys", getAllSet.size());
if (!getAllSet.isEmpty() && childRegion != null) {
childRegion.getAll(getAllSet);
}
}
private boolean isPrefetchPossible() {
return this.childEntityName != null;
}
private void printRegionContents(Region<Object, EntityWrapper> r) {
log.debug("printing contents of {} ",r);
for (Object k : r.keySet()) {
log.debug("key {} value {} ",k,r.get(k));
}
}
@Override
public boolean putFromLoad(Object key, Object value, long txTimestamp,
Object version) throws CacheException {
return putFromLoad(key, value, txTimestamp, version, true);
}
@Override
public boolean putFromLoad(Object key, Object value, long txTimestamp,
Object version, boolean minimalPutOverride) throws CacheException {
EntityWrapper wrapper = new EntityWrapper(value, 1L);
log.debug("putting a new collection entry from load {} value: {}",key, wrapper);
boolean remove = false;
try {
this.region.getGemFireRegion().create(key, wrapper);
} catch (EntryExistsException ee) {
log.debug("key {} exists in the cache already, destroying", key);
remove = true;
} catch (CacheWriterException writerEx) {
this.region.getStats().incHibernateDestroyJobsScheduled();
log.debug("caught a CacheWriterException {} ",writerEx.getMessage());
remove = true;
} catch (ServerOperationException serverEx) {
if (serverEx.getCause() instanceof CacheWriterException) {
this.region.getStats().incHibernateDestroyJobsScheduled();
log.debug("caught a ServerOperationException caused by CacheWriterException {} ",serverEx.getMessage());
} else {
throw serverEx;
}
remove = true;
}
if (remove) {
this.region.getGemFireRegion().remove(key);
return false;
}
return true;
}
@Override
public SoftLock lockItem(Object key, Object version) throws CacheException {
// there are no updates to the collectionCache,
// so no need to lock/version
return null;
}
@Override
public SoftLock lockRegion() throws CacheException {
return null;
}
@Override
public void unlockItem(Object key, SoftLock lock) throws CacheException {
}
@Override
public void unlockRegion(SoftLock lock) throws CacheException {
}
@Override
public void remove(Object key) throws CacheException {
log.debug("removing key {}",key);
this.region.getGemFireRegion().remove(key);
}
@Override
public void removeAll() throws CacheException {
log.debug("removing all keys");
this.region.getGemFireRegion().clear();
}
@Override
public void evict(Object key) throws CacheException {
// TODO we should implement a method on Region to evict
// a particular entry, destroying is inefficient
log.debug("removing key {}", key);
this.region.getGemFireRegion().remove(key);
}
@Override
public void evictAll() throws CacheException {
log.debug("removing all keys");
this.region.getGemFireRegion().clear();
}
}