All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.

There is a newer version: 2.10.9.2
Show newest version
/*
 * 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())); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy