Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.terracotta.modules.ehcache.store.ClusteredStore Maven / Gradle / Ivy
Go to download
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
*/
package org.terracotta.modules.ehcache.store;
import static net.sf.ehcache.statistics.StatisticBuilder.operation;
import net.sf.ehcache.CacheEntry;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheOperationOutcomes.EvictionOutcome;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.ElementData;
import net.sf.ehcache.Status;
import net.sf.ehcache.cluster.CacheCluster;
import net.sf.ehcache.cluster.ClusterNode;
import net.sf.ehcache.cluster.ClusterTopologyListener;
import net.sf.ehcache.concurrent.CacheLockProvider;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.CacheConfiguration.TransactionalMode;
import net.sf.ehcache.config.ConfigError;
import net.sf.ehcache.config.InvalidConfigurationException;
import net.sf.ehcache.config.PinningConfiguration;
import net.sf.ehcache.config.TerracottaConfiguration;
import net.sf.ehcache.config.TerracottaConfiguration.Consistency;
import net.sf.ehcache.event.RegisteredEventListeners;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Results;
import net.sf.ehcache.search.SearchException;
import net.sf.ehcache.search.attribute.AttributeExtractor;
import net.sf.ehcache.store.ElementValueComparator;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import net.sf.ehcache.store.Policy;
import net.sf.ehcache.store.StoreListener;
import net.sf.ehcache.store.StoreQuery;
import net.sf.ehcache.store.TerracottaStore;
import net.sf.ehcache.terracotta.TerracottaNotRunningException;
import net.sf.ehcache.util.SetAsList;
import net.sf.ehcache.writer.CacheWriterManager;
import net.sf.ehcache.writer.writebehind.WriteBehind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.modules.ehcache.ClusteredCacheInternalContext;
import org.terracotta.modules.ehcache.ToolkitInstanceFactory;
import org.terracotta.modules.ehcache.concurrency.TCCacheLockProvider;
import org.terracotta.statistics.Statistic;
import org.terracotta.statistics.observer.OperationObserver;
import org.terracotta.toolkit.ToolkitFeatureTypeInternal;
import org.terracotta.toolkit.atomic.ToolkitTransaction;
import org.terracotta.toolkit.atomic.ToolkitTransactionController;
import org.terracotta.toolkit.atomic.ToolkitTransactionType;
import org.terracotta.toolkit.cache.ToolkitCacheListener;
import org.terracotta.toolkit.collections.ToolkitMap;
import org.terracotta.toolkit.concurrent.locks.ToolkitLock;
import org.terracotta.toolkit.concurrent.locks.ToolkitReadWriteLock;
import org.terracotta.toolkit.internal.ToolkitInternal;
import org.terracotta.toolkit.internal.cache.ToolkitCacheInternal;
import org.terracotta.toolkit.internal.cache.ToolkitValueComparator;
import org.terracotta.toolkit.internal.concurrent.locks.ToolkitLockTypeInternal;
import org.terracotta.toolkit.store.ToolkitConfigFields;
import java.io.Serializable;
import java.util.ArrayList;
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 java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.event.EventListenerList;
public class ClusteredStore implements TerracottaStore, StoreListener {
private static final Logger LOG = LoggerFactory
.getLogger(ClusteredStore.class
.getName());
private static final String CHECK_CONTAINS_KEY_ON_PUT_PROPERTY_NAME = "ehcache.clusteredStore.checkContainsKeyOnPut";
private static final String TRANSACTIONAL_MODE = "trasactionalMode";
private static final String LEADER_ELECTION_LOCK_NAME = "SERVER-EVENT-SUBSCRIPTION-LOCK";
protected static final String LEADER_NODE_ID = "LEADER-NODE-ID";
// final protected fields
protected final ToolkitCacheInternal backend;
protected final ValueModeHandler valueModeHandler;
protected final ToolkitInstanceFactory toolkitInstanceFactory;
protected final Ehcache cache;
protected final String fullyQualifiedCacheName;
// final private fields
private final boolean checkContainsKeyOnPut;
private final int localKeyCacheMaxsize;
private final CacheConfiguration.TransactionalMode transactionalMode;
private final Map keyLookupCache;
private final CacheConfigChangeBridge cacheConfigChangeBridge;
private final RegisteredEventListeners registeredEventListeners;
private final ClusteredCacheInternalContext internalContext;
private final CacheEventListener evictionListener;
// non-final private fields
private EventListenerList listenerList;
private boolean cacheEventListenerRegistered = false;
private final ToolkitLock eventualConcurrentLock;
private final ToolkitLock leaderElectionLock;
private final boolean isEventual;
private final OperationObserver evictionObserver = operation(
EvictionOutcome.class)
.named("eviction")
.of(this)
.build();
private final CacheCluster topology;
private final ToolkitMap configMap;
private final EventListenersRefresher eventListenersRefresher;
private final ToolkitTransactionController transactionController;
private final ToolkitTransactionType transactionType;
public ClusteredStore(ToolkitInstanceFactory toolkitInstanceFactory, Ehcache cache, CacheCluster topology) {
validateConfig(cache);
this.toolkitInstanceFactory = toolkitInstanceFactory;
this.cache = cache;
this.fullyQualifiedCacheName = toolkitInstanceFactory.getFullyQualifiedCacheName(cache);
this.topology = topology;
final CacheConfiguration ehcacheConfig = cache.getCacheConfiguration();
final TerracottaConfiguration terracottaConfiguration = ehcacheConfig.getTerracottaConfiguration();
backend = toolkitInstanceFactory.getOrCreateToolkitCache(cache);
configMap = toolkitInstanceFactory.getOrCreateClusteredStoreConfigMap(cache.getCacheManager().getName(),
cache.getName());
CacheConfiguration.TransactionalMode transactionalModeTemp = (TransactionalMode) configMap.get(TRANSACTIONAL_MODE);
if (transactionalModeTemp == null) {
configMap.putIfAbsent(TRANSACTIONAL_MODE, ehcacheConfig.getTransactionalMode());
transactionalModeTemp = (TransactionalMode) configMap.get(TRANSACTIONAL_MODE);
}
transactionalMode = transactionalModeTemp;
valueModeHandler = ValueModeHandlerFactory.createValueModeHandler(this, ehcacheConfig);
if (terracottaConfiguration.getLocalKeyCache()) {
localKeyCacheMaxsize = terracottaConfiguration.getLocalKeyCacheSize();
keyLookupCache = new ConcurrentHashMap();
} else {
localKeyCacheMaxsize = -1;
keyLookupCache = null;
}
setUpWanConfig();
ToolkitInternal toolkitInternal = (ToolkitInternal) toolkitInstanceFactory.getToolkit();
checkContainsKeyOnPut = toolkitInternal.getProperties().getBoolean(CHECK_CONTAINS_KEY_ON_PUT_PROPERTY_NAME);
LOG.info(getConcurrencyValueLogMsg(cache.getName(),
backend.getConfiguration().getInt(ToolkitConfigFields.CONCURRENCY_FIELD_NAME)));
// connect configurations
cacheConfigChangeBridge = createConfigChangeBridge(toolkitInstanceFactory, cache, backend);
cacheConfigChangeBridge.connectConfigs();
if (LOG.isDebugEnabled()) {
LOG.debug("Initialized " + this.getClass().getName() + " for " + cache.getName());
}
registeredEventListeners = cache.getCacheEventNotificationService();
// per-cache lock to ensure only one client can register a listener
leaderElectionLock = toolkitInstanceFactory.getLockForCache(cache, LEADER_ELECTION_LOCK_NAME);
evictionListener = new CacheEventListener();
eventListenersRefresher = new EventListenersRefresher();
topology.addTopologyListener(eventListenersRefresher);
notifyCacheEventListenersChanged(); // just notify to initialize the registration state
CacheLockProvider cacheLockProvider = new TCCacheLockProvider(backend, valueModeHandler);
internalContext = new ClusteredCacheInternalContext(toolkitInstanceFactory.getToolkit(), cacheLockProvider);
eventualConcurrentLock = toolkitInternal.getLock("EVENTUAL-CONCURRENT-LOCK-FOR-CLUSTERED-STORE",
ToolkitLockTypeInternal.CONCURRENT);
isEventual = (terracottaConfiguration.getConsistency() == Consistency.EVENTUAL);
transactionController = toolkitInternal.getFeature(ToolkitFeatureTypeInternal.TRANSACTION);
transactionType = terracottaConfiguration.isSynchronousWrites() ? ToolkitTransactionType.SYNC
: ToolkitTransactionType.NORMAL;
}
void setUpWanConfig() {
if (!cache.getCacheManager().getConfiguration().getTerracottaConfiguration().isWanEnabledTSA()) {
toolkitInstanceFactory.markCacheWanDisabled(cache.getCacheManager().getName(), cache.getName());
}
}
public String getFullyQualifiedCacheName() {
return fullyQualifiedCacheName;
}
private static CacheConfigChangeBridge createConfigChangeBridge(ToolkitInstanceFactory toolkitInstanceFactory,
Ehcache ehcache,
ToolkitCacheInternal cache) {
return new CacheConfigChangeBridge(toolkitInstanceFactory.getFullyQualifiedCacheName(ehcache), cache,
toolkitInstanceFactory.getOrCreateConfigChangeNotifier(ehcache), ehcache.getCacheConfiguration());
}
private static void validateConfig(Ehcache ehcache) {
CacheConfiguration cacheConfiguration = ehcache.getCacheConfiguration();
final TerracottaConfiguration terracottaConfiguration = cacheConfiguration.getTerracottaConfiguration();
List errors = new ArrayList();
if (terracottaConfiguration == null || !terracottaConfiguration.isClustered()) { throw new InvalidConfigurationException(
"Cannot create clustered store for non-terracotta clustered caches"); }
MemoryStoreEvictionPolicy policy = cacheConfiguration.getMemoryStoreEvictionPolicy();
if (policy == MemoryStoreEvictionPolicy.FIFO || policy == MemoryStoreEvictionPolicy.LFU) {
errors.add(new ConfigError("Policy '" + policy + "' is not a supported memory store eviction policy."));
}
if (cacheConfiguration.isOverflowToDisk()) {
if (LOG.isWarnEnabled()) {
LOG.warn("Persistence on disk on the local node is not supported with a Terracotta clustered ehcache store. Configure the Terracotta server array to be persistent instead.");
}
}
boolean cachePinned = cacheConfiguration.getPinningConfiguration() != null
&& cacheConfiguration.getPinningConfiguration().getStore() == PinningConfiguration.Store.INCACHE;
if (cachePinned && cacheConfiguration.getMaxEntriesInCache() != CacheConfiguration.DEFAULT_MAX_ENTRIES_IN_CACHE) {
errors.add(new ConfigError("Cache pinning is not supported with maxEntriesInCache"));
}
if (errors.size() > 0) { throw new InvalidConfigurationException(errors); }
}
@Override
public void recalculateSize(Object key) {
throw new UnsupportedOperationException("Recalculate size is not supported for Terracotta clustered caches.");
}
@Override
public synchronized void addStoreListener(StoreListener listener) {
removeStoreListener(listener);
getEventListenerList().add(StoreListener.class, listener);
}
@Override
public synchronized void removeStoreListener(StoreListener listener) {
getEventListenerList().remove(StoreListener.class, listener);
}
private synchronized EventListenerList getEventListenerList() {
if (listenerList == null) {
listenerList = new EventListenerList();
// TODO: do we still need to support sending notifications when bulk-load turns on/off
}
return listenerList;
}
@Override
public void clusterCoherent(final boolean clusterCoherent) {
Object[] listeners = getEventListenerList().getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == StoreListener.class) {
((StoreListener) listeners[i + 1]).clusterCoherent(clusterCoherent);
}
}
}
@Override
public boolean put(Element element) throws CacheException {
return putInternal(element);
}
@Override
public boolean putWithWriter(Element element, CacheWriterManager writerManager) throws CacheException {
if (element == null) { return true; }
String pKey = generatePortableKeyFor(element.getObjectKey());
// extractSearchAttributes(element);
ToolkitTransaction transaction = transactionController.beginTransaction(transactionType);
try {
ToolkitLock lock = getLockForKey(pKey);
lock.lock();
try {
writerManager.put(element);
return putInternal(element);
} finally {
lock.unlock();
}
} finally {
transaction.commit();
}
}
private boolean putInternal(Element element) throws CacheException {
if (element == null) { return true; }
String pKey = generatePortableKeyFor(element.getObjectKey());
if (element.usesCacheDefaultLifespan()) {
return doPut(pKey, element);
} else {
return doPutWithCustomLifespan(pKey, element);
}
}
@Override
public void putAll(Collection elements) throws CacheException {
Map entries = new HashMap();
for (Element element : elements) {
String pKey = generatePortableKeyFor(element.getObjectKey());
if (!element.usesCacheDefaultLifespan()) {
doPutWithCustomLifespan(pKey, element);
} else {
// extractSearchAttributes(element);
ElementData elementData = valueModeHandler.createElementData(element);
entries.put(pKey, elementData);
}
}
backend.putAll(entries);
}
@Override
public Element get(Object key) {
Object pKey = generatePortableKeyFor(key);
Serializable value = backend.get(pKey);
if (value == null) { return null; }
return this.valueModeHandler.createElement(key, value);
}
@Override
public Element getQuiet(Object key) {
String pKey = generatePortableKeyFor(key);
Serializable value = backend.getQuiet(pKey);
if (value == null) { return null; }
return this.valueModeHandler.createElement(key, value);
}
@Override
public List getKeys() {
return Collections.unmodifiableList(new SetAsList(new RealObjectKeySet(this.valueModeHandler, backend.keySet())));
}
@Override
public Element remove(Object key) {
if (key == null) { return null; }
String pKey = generatePortableKeyFor(key);
Serializable value = backend.remove(pKey);
Element element = this.valueModeHandler.createElement(key, value);
if (keyLookupCache != null) {
keyLookupCache.remove(key);
}
if (element != null) {
return element;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(cache.getName() + " Cache: Cannot remove entry as key " + key + " was not found");
}
return null;
}
}
@Override
public void removeAll(Collection> keys) {
Set entries = new HashSet();
for (Object key : keys) {
String pKey = generatePortableKeyFor(key);
entries.add(pKey);
}
backend.removeAll(entries);
}
@Override
public Element removeWithWriter(Object key, CacheWriterManager writerManager) throws CacheException {
if (key == null) { return null; }
String pKey = generatePortableKeyFor(key);
ToolkitLock lock = getLockForKey(pKey);
ToolkitTransaction transaction = transactionController.beginTransaction(transactionType);
try {
lock.lock();
try {
writerManager.remove(new CacheEntry(key, get(key)));
return remove(key);
} finally {
lock.unlock();
}
} finally {
transaction.commit();
}
}
@Override
public void removeAll() throws CacheException {
backend.clear();
if (keyLookupCache != null) {
keyLookupCache.clear();
}
}
@Override
public Element putIfAbsent(Element element) throws NullPointerException {
String pKey = generatePortableKeyFor(element.getObjectKey());
// extractSearchAttributes(element);
ElementData value = valueModeHandler.createElementData(element);
Serializable data = backend.putIfAbsent(pKey, value);
return data == null ? null : this.valueModeHandler.createElement(element.getKey(), data);
}
@Override
public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException {
if (isEventual) {
return removeElementEventual(element, comparator);
}
String pKey = generatePortableKeyFor(element.getKey());
ToolkitReadWriteLock lock = backend.createLockForKey(pKey);
lock.writeLock().lock();
try {
Element oldElement = getQuiet(element.getKey());
if (comparator.equals(oldElement, element)) { return remove(element.getKey()); }
} finally {
lock.writeLock().unlock();
}
return null;
}
private Element removeElementEventual(Element element, ElementValueComparator comparator) {
String pKey = generatePortableKeyFor(element.getKey());
ElementData value = valueModeHandler.createElementData(element);
if (backend.remove(pKey, value, new ElementValueComparatorToolkitWrapper(element.getObjectKey(), comparator))) {
return element;
} else {
return null;
}
}
@Override
public boolean replace(Element old, Element element, ElementValueComparator comparator) throws NullPointerException,
IllegalArgumentException {
if (isEventual) {
return replaceEventual(old, element, comparator);
}
String pKey = generatePortableKeyFor(element.getKey());
ToolkitReadWriteLock lock = backend.createLockForKey(pKey);
lock.writeLock().lock();
try {
Element oldElement = getQuiet(element.getKey());
if (comparator.equals(oldElement, old)) { return putInternal(element); }
} finally {
lock.writeLock().unlock();
}
return false;
}
private boolean replaceEventual(Element old, Element element, ElementValueComparator comparator) {
String pKey = generatePortableKeyFor(element.getKey());
ElementData oldValue = valueModeHandler.createElementData(old);
ElementData value = valueModeHandler.createElementData(element);
return backend.replace(pKey, oldValue, value, new ElementValueComparatorToolkitWrapper(old.getObjectKey(), comparator));
}
@Override
public Element replace(Element element) throws NullPointerException {
if (isEventual) {
return replaceEventual(element);
}
String pKey = generatePortableKeyFor(element.getKey());
ToolkitReadWriteLock lock = backend.createLockForKey(pKey);
lock.writeLock().lock();
try {
Element oldElement = getQuiet(element.getKey());
if (oldElement != null) {
putInternal(element);
}
return oldElement;
} finally {
lock.writeLock().unlock();
}
}
private Element replaceEventual(Element element) {
String pKey = generatePortableKeyFor(element.getKey());
ElementData value = valueModeHandler.createElementData(element);
return valueModeHandler.createElement(element.getObjectKey(), backend.replace(pKey, value));
}
@Override
public void dispose() {
try {
dropLeaderStatus();
topology.removeTopologyListener(eventListenersRefresher);
backend.removeListener(evictionListener);
backend.disposeLocally();
} catch (RuntimeException e) {
if(e.getClass().getSimpleName().equals("TCNotRunningException")) {
LOG.info("Terracotta client already shutdown", e);
} else {
throw e;
}
}
cacheConfigChangeBridge.disconnectConfigs();
toolkitInstanceFactory.removeNonStopConfigforCache(cache);
}
@Override
public int getSize() {
return getTerracottaClusteredSize();
}
@Override
@Statistic(name = "size", tags = "local-heap")
public int getInMemorySize() {
return backend.localOnHeapSize();
}
@Override
@Statistic(name = "size", tags = "local-offheap")
public int getOffHeapSize() {
return backend.localOffHeapSize();
}
@Override
public int getOnDiskSize() {
return 0;
}
@Override
public void quickClear() {
backend.quickClear();
}
@Override
@Statistic(name = "size", tags = "remote")
public int quickSize() {
return backend.quickSize();
}
@Override
public int getTerracottaClusteredSize() {
return backend.size();
}
@Override
@Statistic(name = "size-in-bytes", tags = "local-heap")
public long getInMemorySizeInBytes() {
return backend.localOnHeapSizeInBytes();
}
@Override
@Statistic(name = "size-in-bytes", tags = "local-offheap")
public long getOffHeapSizeInBytes() {
return backend.localOffHeapSizeInBytes();
}
@Override
public long getOnDiskSizeInBytes() {
return 0;
}
@Override
public boolean hasAbortedSizeOf() {
return false;
}
@Override
public Status getStatus() {
return Status.STATUS_ALIVE;
}
@Override
public boolean containsKey(Object key) {
Object pKey = generatePortableKeyFor(key);
return backend.containsKey(pKey);
}
@Override
public boolean containsKeyOnDisk(Object key) {
return false;
}
@Override
public boolean containsKeyOffHeap(Object key) {
String pKey = generatePortableKeyFor(key);
return backend.containsKeyLocalOffHeap(pKey);
}
@Override
public boolean containsKeyInMemory(Object key) {
String pKey = generatePortableKeyFor(key);
return backend.containsKeyLocalOnHeap(pKey);
}
/**
* Expire all elements.
*
* This is a default implementation which does nothing. Expiration on demand is only implemented for disk stores.
*/
@Override
public void expireElements() {
// empty implementation
}
@Override
public void flush() {
// should be emptied if clearOnFlush is true
if (cache.getCacheConfiguration().isClearOnFlush()) {
backend.clear();
if (keyLookupCache != null) {
keyLookupCache.clear();
}
}
}
@Override
public boolean bufferFull() {
return false;
}
@Override
public Policy getInMemoryEvictionPolicy() {
// memory store eviction policy not configurable for clustered stores
throw new UnsupportedOperationException();
}
@Override
public void setInMemoryEvictionPolicy(Policy policy) {
// memory store eviction policy not configurable for clustered stores
throw new UnsupportedOperationException();
}
@Override
public Object getInternalContext() {
return internalContext;
}
@Override
public boolean isCacheCoherent() {
return isClusterCoherent();
}
@Override
public boolean isClusterCoherent() throws TerracottaNotRunningException {
return !backend.isBulkLoadEnabled();
}
@Override
public boolean isNodeCoherent() throws TerracottaNotRunningException {
return !backend.isNodeBulkLoadEnabled();
}
@Override
public void setNodeCoherent(boolean coherent) throws UnsupportedOperationException, TerracottaNotRunningException {
backend.setNodeBulkLoadEnabled(!coherent);
}
@Override
public void waitUntilClusterCoherent() throws UnsupportedOperationException, TerracottaNotRunningException,
InterruptedException {
backend.waitUntilBulkLoadComplete();
}
@Override
public Object getMBean() {
return null;
}
@Override
public void setAttributeExtractors(Map extractors) {
if (!extractors.isEmpty()) { throw new CacheException("Search attributes only supported in enterprise edition"); }
}
@Override
public Results executeQuery(StoreQuery query) throws SearchException {
throw new UnsupportedOperationException("Search execution unsupported in non-enterprise edition");
}
@Override
public Set getSearchAttributes() {
return Collections.emptySet();
}
@Override
public Attribute getSearchAttribute(String attributeName) {
return null;
}
@Override
public Map getAllQuiet(Collection> keys) {
return doGetAll(keys, true);
}
@Override
public Map getAll(Collection> keys) {
return doGetAll(keys, false);
}
private Map doGetAll(Collection> keys, boolean quiet) {
List pKeys = new ArrayList(keys.size());
for (Object key : keys) {
pKeys.add(generatePortableKeyFor(key));
}
final Map values;
if (quiet) {
values = backend.getAllQuiet(pKeys);
} else {
values = backend.getAll(pKeys);
}
Map elements = new HashMap();
Set> entrySet = values.entrySet();
for (Map.Entry entry : entrySet) {
Object key = this.valueModeHandler.getRealKeyObject(entry.getKey());
elements.put(key, this.valueModeHandler.createElement(key, entry.getValue()));
}
return elements;
}
/**
* Generates a portable key for the supplied object.
*/
public String generatePortableKeyFor(final Object obj) {
boolean useCache = shouldUseCache(obj);
if (useCache) {
String value = keyLookupCache.get(obj);
if (value != null) { return value; }
}
String key;
try {
key = this.valueModeHandler.createPortableKey(obj);
} catch (Exception e) {
throw new CacheException(e);
}
if (useCache && keyLookupCache.size() < localKeyCacheMaxsize) {
keyLookupCache.put(obj, key);
}
return key;
}
private boolean shouldUseCache(final Object obj) {
// no sense putting existing String keys into the soft cache
return keyLookupCache != null && !(obj instanceof String);
}
private boolean doPut(String portableKey, Element element) {
ElementData value = valueModeHandler.createElementData(element);
if (checkContainsKeyOnPut) {
return backend.put(portableKey, value) == null;
} else {
backend.putNoReturn(portableKey, value);
return true;
}
}
private boolean doPutWithCustomLifespan(String portableKey, Element element) {
ElementData value = valueModeHandler.createElementData(element);
int creationTimeInSecs = (int) (element.getCreationTime() / 1000);
int customTTI = element.isEternal() ? Integer.MAX_VALUE : element.getTimeToIdle();
int customTTL = element.isEternal() ? Integer.MAX_VALUE : element.getTimeToLive();
if (checkContainsKeyOnPut) {
return backend.put(portableKey, value, creationTimeInSecs, customTTI, customTTL) == null;
} else {
backend.putNoReturn(portableKey, value, creationTimeInSecs, customTTI, customTTL);
return true;
}
}
@Override
public Element unsafeGet(Object key) {
String pKey = generatePortableKeyFor(key);
Serializable value = backend.unsafeLocalGet(pKey);
if (value == null) { return null; }
return this.valueModeHandler.createElement(key, value);
}
@Override
public Set getLocalKeys() {
return Collections.unmodifiableSet(new RealObjectKeySet(valueModeHandler, backend.localKeySet()));
}
@Override
public TransactionalMode getTransactionalMode() {
return transactionalMode;
}
public boolean isSearchable() {
return false;
}
public String getLeader() {
return (String) configMap.get(LEADER_NODE_ID);
}
private boolean isThisNodeLeader() {
return topology.getCurrentNode().getId().equals(getLeader());
}
private void dropLeaderStatus() {
leaderElectionLock.lock();
try {
if (isThisNodeLeader()) {
configMap.remove(LEADER_NODE_ID);
}
} finally {
leaderElectionLock.unlock();
}
}
private void electLeaderIfNecessary() {
String leader;
while ((leader = getLeader()) == null || isNotInCluster(leader)) {
if (leaderElectionLock.tryLock()) {
try {
final String id = topology.getCurrentNode().getId();
configMap.put(LEADER_NODE_ID, id);
if (LOG.isDebugEnabled()) {
LOG.debug("New server event acceptor elected: " + id);
}
} finally {
leaderElectionLock.unlock();
}
}
}
}
private boolean isNotInCluster(String nodeId) {
for (ClusterNode node : topology.getNodes()) {
if (node.getId().equals(nodeId)) { return false; }
}
return true;
}
private class CacheEventListener implements ToolkitCacheListener {
@Override
public void onEviction(String key) {
evictionObserver.begin();
evictionObserver.end(EvictionOutcome.SUCCESS);
electLeaderIfNecessary();
// only leader handles server events
if (isThisNodeLeader()) {
Element element = new Element(valueModeHandler.getRealKeyObject(key), null);
registeredEventListeners.notifyElementEvicted(element, false);
}
}
@Override
public void onExpiration(String key) {
electLeaderIfNecessary();
// only leader handles server events
if (isThisNodeLeader()) {
Element element = new Element(valueModeHandler.getRealKeyObject(key), null);
registeredEventListeners.notifyElementExpiry(element, false);
}
}
}
private class EventListenersRefresher implements ClusterTopologyListener {
@Override
public void nodeJoined(final ClusterNode node) {
//
}
@Override
public void nodeLeft(final ClusterNode node) {
//
}
@Override
public void clusterOnline(final ClusterNode node) {
// Need to refresh once when cluster is online again in case listener unregister failure due to cluster disconnect
notifyCacheEventListenersChanged();
}
@Override
public void clusterOffline(final ClusterNode node) {
//
}
@Override
public void clusterRejoined(final ClusterNode oldNode, final ClusterNode newNode) {
//
}
}
// tests assert on the log msg printed
private static String getConcurrencyValueLogMsg(String name, int concurrency) {
return "Cache [" + name + "] using concurrency: " + concurrency;
}
@Override
public WriteBehind createWriteBehind() {
throw new UnsupportedOperationException();
}
private ToolkitLock getLockForKey(String pKey) {
if (isEventual) {
return eventualConcurrentLock;
} else {
return backend.createLockForKey(pKey).writeLock();
}
}
@Override
public synchronized void notifyCacheEventListenersChanged() {
if (cache.getCacheEventNotificationService().hasCacheEventListeners() && !cacheEventListenerRegistered) {
backend.addListener(evictionListener);
cacheEventListenerRegistered = true;
} else if (!cache.getCacheEventNotificationService().hasCacheEventListeners() && cacheEventListenerRegistered) {
dropLeaderStatus();
backend.removeListener(evictionListener);
cacheEventListenerRegistered = false;
}
}
private static class ElementValueComparatorToolkitWrapper implements ToolkitValueComparator {
private final Object key;
private final ElementValueComparator wrappedComparator;
private ElementValueComparatorToolkitWrapper(Object key, ElementValueComparator wrappedComparator) {
this.key = key;
this.wrappedComparator = wrappedComparator;
}
@Override
public boolean equals(Serializable serializable, Serializable serializable2) {
ElementData val1 = (ElementData)serializable;
ElementData val2 = (ElementData)serializable2;
return wrappedComparator.equals(new Element(key, val1.getValue()),
new Element(key, val2.getValue()));
}
}
}