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

org.infinispan.jcache.embedded.JCache Maven / Gradle / Ivy

package org.infinispan.jcache.embedded;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CompletionListener;
import javax.cache.management.CacheStatisticsMXBean;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.management.MBeanServer;

import org.infinispan.AdvancedCache;
import org.infinispan.commons.CacheListenerException;
import org.infinispan.commons.api.AsyncCache;
import org.infinispan.commons.marshall.StreamingMarshaller;
import org.infinispan.commons.util.InfinispanCollections;
import org.infinispan.commons.util.ReflectionUtil;
import org.infinispan.commons.util.concurrent.FutureListener;
import org.infinispan.context.Flag;
import org.infinispan.jcache.AbstractJCache;
import org.infinispan.jcache.AbstractJCacheListenerAdapter;
import org.infinispan.jcache.Exceptions;
import org.infinispan.jcache.JCacheEntry;
import org.infinispan.jcache.MutableJCacheEntry;
import org.infinispan.jcache.embedded.logging.Log;
import org.infinispan.jmx.JmxUtil;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.manager.PersistenceManagerImpl;
import org.infinispan.util.concurrent.locks.impl.LockContainer;
import org.infinispan.util.concurrent.locks.impl.PerKeyLockContainer;
import org.infinispan.util.logging.LogFactory;

/**
 * Infinispan's implementation of {@link javax.cache.Cache} interface.
 *
 * @author Vladimir Blagojevic
 * @author Galder Zamarreño
 * @since 5.3
 */
public class JCache extends AbstractJCache {

   private static final Log log =
         LogFactory.getLog(JCache.class, Log.class);
   private static final boolean trace = log.isTraceEnabled();

   private final AdvancedCache cache;
   private final AdvancedCache ignoreReturnValuesCache;
   private final AdvancedCache skipCacheLoadCache;
   private final AdvancedCache skipCacheLoadAndStatsCache;
   private final AdvancedCache skipListenerCache;
   private final AdvancedCache skipStatisticsCache;
   private final RICacheStatistics stats;

   private final LockContainer processorLocks;
   private final long lockTimeout; // milliseconds

   public JCache(AdvancedCache cache, CacheManager cacheManager, ConfigurationAdapter c) {
      super(c.getConfiguration(), cacheManager, new JCacheNotifier());
      this.cache = cache;
      this.processorLocks = new PerKeyLockContainer(32, cache.getCacheConfiguration().dataContainer().keyEquivalence());
      ((PerKeyLockContainer) processorLocks).inject(cache.getComponentRegistry().getTimeService());
      this.ignoreReturnValuesCache = cache.withFlags(Flag.IGNORE_RETURN_VALUES);
      this.skipCacheLoadCache = cache.withFlags(Flag.SKIP_CACHE_LOAD);
      this.skipCacheLoadAndStatsCache = cache.withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_STATISTICS);
      // Typical use cases of the SKIP_LISTENER_NOTIFICATION is when trying
      // to comply with specifications such as JSR-107, which mandate that
      // {@link Cache#clear()}} calls do not fire entry removed notifications
      this.skipListenerCache = cache.withFlags(Flag.SKIP_LISTENER_NOTIFICATION);
      this.skipStatisticsCache = cache.withFlags(Flag.SKIP_STATISTICS);

      this.stats = new RICacheStatistics(this.cache);
      this.lockTimeout =  cache.getCacheConfiguration()
            .locking().lockAcquisitionTimeout();

      addConfigurationListeners();

      setCacheLoader(configuration);
      setCacheWriter(configuration);

      if (configuration.isManagementEnabled())
         setManagementEnabled(true);

      if (configuration.isStatisticsEnabled())
         setStatisticsEnabled(true);
   }

   protected void addCacheLoaderAdapter(CacheLoader cacheLoader) {
      PersistenceManagerImpl persistenceManager =
            (PersistenceManagerImpl) cache.getComponentRegistry().getComponent(PersistenceManager.class);
      JCacheLoaderAdapter adapter = getCacheLoaderAdapter(persistenceManager);
      adapter.setCacheLoader(jcacheLoader);
      adapter.setExpiryPolicy(expiryPolicy);
   }

   @Override
   protected void addCacheWriterAdapter(CacheWriter cacheWriter) {
      PersistenceManagerImpl persistenceManager =
            (PersistenceManagerImpl) cache.getComponentRegistry().getComponent(PersistenceManager.class);
      JCacheWriterAdapter ispnCacheStore = getCacheWriterAdapter(persistenceManager);
      ispnCacheStore.setCacheWriter(jcacheWriter);
   }

   @SuppressWarnings("unchecked")
   private JCacheLoaderAdapter getCacheLoaderAdapter(PersistenceManagerImpl persistenceManager) {
      return (JCacheLoaderAdapter) persistenceManager.getAllLoaders().get(0);
   }

   @SuppressWarnings("unchecked")
   private JCacheWriterAdapter getCacheWriterAdapter(PersistenceManagerImpl persistenceManager) {
      return (JCacheWriterAdapter) persistenceManager.getAllWriters().get(0);
   }

   @Override
   public void clear() {
      // TCK expects clear() to not fire any remove events
      skipListenerCache.clear();
   }

   @Override
   public boolean containsKey(final K key) {
      checkNotClosed();

      if (trace)
         log.tracef("Invoke containsKey(key=%s)", key);

      if (key == null)
         throw log.parameterMustNotBeNull("key");

      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return skipCacheLoadCache.containsKey(key);
            }
         });
      }

      return skipCacheLoadAndStatsCache.containsKey(key);
   }

   @Override
   public V get(final K key) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public V call() {
               return doGet(key);
            }
         });
      }

      return doGet(key);
   }

   private V doGet(K key) {
      V value = configuration.isReadThrough() ? cache.get(key) : skipCacheLoadCache.get(key);
      if (value != null)
         updateTTLForAccessed(cache, key, value);

      return value;
   }

   @Override
   public Map getAll(Set keys) {
      checkNotClosed();
      verifyKeys(keys);
      if (keys.isEmpty()) {
         return Collections.emptyMap();
      }

      AdvancedCache cache = configuration.isReadThrough() ? this.cache : 
         this.skipCacheLoadCache;
      return cache.getAll(keys);
   }

   @Override
   public V getAndPut(final K key, final V value) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public V call() {
               return put(skipCacheLoadCache, skipCacheLoadCache, key, value, false);
            }
         });
      }
      return put(skipCacheLoadCache, skipCacheLoadCache, key, value, false);
   }

   @Override
   public V getAndRemove(final K key) {
      checkNotClosed();
      skipCacheLoadCache.get(key); // bring in key and update stats
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public V call() {
               return skipCacheLoadCache.remove(key);
            }
         });
      }

      return skipCacheLoadCache.remove(key);
   }

   @Override
   public V getAndReplace(final K key, final V value) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public V call() {
               return replace(skipCacheLoadCache, key, value);
            }
         });
      }

      return replace(skipCacheLoadCache, key, value);
   }

   @Override
   public void close() {
      cache.stop();
   }

   @Override
   public boolean isClosed() {
      return cache.getStatus().isTerminated();
   }

   @Override
   public String getName() {
      return cache.getName();
   }

   @Override
   public  T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments) {
      checkNotClosed().checkNotNull(key, "key").checkNotNull(entryProcessor, "entryProcessor");

      // Using references for backup copies to provide perceived exclusive
      // read access, and only apply changes if original value was not
      // changed by another thread, the JSR requirements for this method could
      // have been full filled. However, the TCK has some timing checks which
      // verify that under contended access, one of the threads should "wait"
      // for the other, hence the use locks.

      if (trace)
         log.tracef("Invoke entry processor %s for key=%s", entryProcessor, key);

      return new WithProcessorLock().call(key, new Callable() {
         @Override
         public T call() throws Exception {
            // Get old value skipping any listeners to impacting
            // listener invocation expectations set by the TCK.
            V oldValue = skipCacheLoadCache.get(key);
            V safeOldValue = oldValue;
            if (configuration.isStoreByValue()) {
               // Make a copy because the entry processor could make changes
               // directly in the value, and we wanna keep a safe old value
               // around for when calling the atomic replace() call.
               safeOldValue = safeCopy(oldValue);
            }

            MutableJCacheEntry mutable = createMutableCacheEntry(safeOldValue, key);
            T ret = processEntryProcessor(mutable, entryProcessor, arguments);

            switch (mutable.getOperation()) {
               case NONE:
                  break;
               case ACCESS:
                  updateTTLForAccessed(cache, key, oldValue);
                  break;
               case UPDATE:
                  V newValue = mutable.getNewValue();
                  if (oldValue != null) {
                     // Only allow change to be applied if value has not
                     // changed since the start of the processing.
                     replace(cache, skipCacheLoadAndStatsCache, key, oldValue, newValue, true);
                  } else {
                     put(cache, skipCacheLoadCache, key, newValue, true);
                  }
                  break;
               case REMOVE:
                  cache.remove(key);
                  break;
               default:
                  break;
            }

            return ret;
         }
      });
   }

   private MutableJCacheEntry createMutableCacheEntry(V safeOldValue, K key) {
      return new MutableJCacheEntry(
            configuration.isReadThrough() ? cache : skipCacheLoadCache, skipStatisticsCache, key, safeOldValue);
   }


   @SuppressWarnings("unchecked")
   private V safeCopy(V original) {
      try {
         StreamingMarshaller marshaller = skipCacheLoadCache.getComponentRegistry().getCacheMarshaller();
         byte[] bytes = marshaller.objectToByteBuffer(original);
         Object o = marshaller.objectFromByteBuffer(bytes);
         return (V) o;
      } catch (Exception e) {
         throw new CacheException(
               "Unexpected error making a copy of entry " + original, e);
      }
   }

   private boolean lockRequired(K key) {
      // Check if processor is locking a key, so that exclusive locking can
      // be avoided for majority of use cases. This way, only when
      // invokeProcessor is locking a key there's a need for CRUD cache
      // methods to acquire the exclusive lock. This latter requirement is
      // specifically tested by the TCK comparing duration of paralell
      // executions.
      boolean locked = processorLocks.isLocked(key);
      if (trace)
         log.tracef("Lock required for key=%s? %s", key, locked);

      return locked;
   }

   private void acquiredProcessorLock(K key) throws InterruptedException {
      processorLocks.acquire(key, Thread.currentThread(), lockTimeout, TimeUnit.MILLISECONDS);
   }

   private void releaseProcessorLock(K key) {
      processorLocks.release(key, Thread.currentThread());
   }

   @Override
   public Iterator> iterator() {
      if (isClosed()) {
         throw log.cacheClosed(cache.getStatus());
      }
      return new Itr();
   }

   @Override
   public void loadAll(Set keys, boolean replaceExistingValues, final CompletionListener listener) {
      checkNotClosed();

      if (keys == null)
         throw log.parameterMustNotBeNull("keys");

      // Separate logic based on whether a JCache cache loader or an Infinispan
      // cache loader might be plugged. TCK has some strict expectations when
      // it comes to expiry policy usage when loaded entries are stored in the
      // cache for the first time, or if they're overriding a value (forced by
      // replaceExistingValues). These two cases cannot be discerned at the
      // cache loader level. The only alternative way would be for a jcache
      // loader interceptor to be created, but getting the interaction right
      // with existing cache loader interceptor would not be trivial. Hence,
      // we separate logic at this level.

      if (jcacheLoader == null && jcacheWriter != null)
         setListenerCompletion(listener);
      else if (jcacheLoader != null) {
         loadAllFromJCacheLoader(keys, replaceExistingValues, listener, ignoreReturnValuesCache.withFlags(Flag.SKIP_CACHE_STORE), skipCacheLoadCache);
      } else
         loadAllFromInfinispanCacheLoader(keys, replaceExistingValues, listener);
   }

   @Override
   public void put(final K key, final V value) {
      checkNotClosed();
      if (lockRequired(key)) {
         new WithProcessorLock().call(key, new Callable() {
            @Override
            public Void call() {
               doPut(key, value);
               return null;
            }
         });
      } else {
         doPut(key, value);
      }
   }

   @Override
   public void putAll(Map inputMap) {
      checkNotClosed();
      InfinispanCollections.assertNotNullEntries(inputMap, "inputMap");  // spec required check
      /**
       * TODO Similar to mentioned before, it'd be interesting to see if multiple putAsync() calls
       * could be executed in parallel to speed up.
       *
       */
      for (final Map.Entry e : inputMap.entrySet()) {
         final K key = e.getKey();
         if (lockRequired(key)) {
            new WithProcessorLock().call(key, new Callable() {
               @Override
               public Void call() {
                  doPut(key, e.getValue());
                  return null;
               }
            });
         } else {
            doPut(key, e.getValue());
         }
      }
   }

   private void doPut(K key, V value) {
      // A normal put should not fire notifications when checking TTL
      put(ignoreReturnValuesCache, skipCacheLoadAndStatsCache, key, value, false);
   }

   @Override
   public boolean putIfAbsent(final K key, final V value) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return put(skipCacheLoadCache,
                     skipCacheLoadAndStatsCache, key, value, true) == null;
            }
         });
      }

      return put(skipCacheLoadCache,
            skipCacheLoadAndStatsCache, key, value, true) == null;
   }

   @Override
   public boolean remove(final K key) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return cache.remove(key) != null;
            }
         });
      }

      try {
         return cache.remove(key) != null;
      } catch (CacheListenerException e) {
         throw Exceptions.launderCacheListenerException(e);
      }
   }

   @Override
   public boolean remove(final K key, final V oldValue) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return remove(cache, key, oldValue);
            }
         });
      }

      return remove(cache, key, oldValue);
   }

   @Override
   public void removeAll() {
      if (isClosed()) {
         throw log.cacheClosed(cache.getStatus());
      }
      // Calling cache.clear() won't work since there's currently no way to
      // for an Infinispan cache store to figure out all keys store and pass
      // them to CacheWriter.deleteAll(), hence, delete individually.
      // TODO: What happens with entries only in store but not in memory?

      // Delete asynchronously and then wait for removals to complete
      List> futures = new ArrayList>();
      for (final K key : cache.keySet()) {
         if (lockRequired(key)) {
            new WithProcessorLock().call(key, new Callable() {
               @Override
               public Void call() {
                  cache.remove(key);
                  return null;
               }
            });
         } else {
            futures.add(cache.removeAsync(key));
         }
      }

      for (Future future : futures) {
         try {
            future.get(10, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new CacheException(
                  "Interrupted while waiting for remove to complete");
         } catch (Exception e) {
            throw Exceptions.launderCacheWriterException(e);
         }
      }
   }

   @Override
   public void removeAll(Set keys) {
      checkNotClosed();
      verifyKeys(keys);
      for (K k : keys) {
         remove(k);
      }
   }

   @Override
   public boolean replace(final K key, final V value) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return replace(skipCacheLoadCache, skipCacheLoadCache,
                     key, null, value, false);
            }
         });
      }

      return replace(skipCacheLoadCache, skipCacheLoadCache,
            key, null, value, false);
   }

   @Override
   public boolean replace(final K key, final V oldValue, final V newValue) {
      checkNotClosed();
      if (lockRequired(key)) {
         return new WithProcessorLock().call(key, new Callable() {
            @Override
            public Boolean call() {
               return replace(skipCacheLoadCache, skipCacheLoadCache,
                     key, oldValue, newValue, true);
            }
         });
      }

      return replace(skipCacheLoadCache, skipCacheLoadCache,
            key, oldValue, newValue, true);
   }

   @Override
   public  T unwrap(Class clazz) {
      return ReflectionUtil.unwrapAny(clazz, this, cache);
   }

   @Override
   public void registerCacheEntryListener(CacheEntryListenerConfiguration listenerCfg) {
      notifier.addListener(listenerCfg, this, notifier);
      addCacheEntryListenerConfiguration(listenerCfg);
   }

   @Override
   public void deregisterCacheEntryListener(CacheEntryListenerConfiguration listenerCfg) {
      notifier.removeListener(listenerCfg, this);
      removeCacheEntryListenerConfiguration(listenerCfg);
   }

   public void setStatisticsEnabled(boolean enabled) {
      cache.getStats().setStatisticsEnabled(enabled);
      super.setStatisticsEnabled(enabled);
   }

   protected CacheStatisticsMXBean getCacheStatisticsMXBean() {
      return stats;
   }

   protected MBeanServer getMBeanServer() {
      return JmxUtil.lookupMBeanServer(
            cache.getCacheManager().getCacheManagerConfiguration());
   }

   protected AbstractJCache checkNotClosed() {
      if (isClosed()) {
         throw log.cacheClosed(cache.getStatus());
      }

      return this;
   }

   private void loadAllFromInfinispanCacheLoader(Set keys, boolean replaceExistingValues, final CompletionListener listener) {
      final List keysToLoad = filterLoadAllKeys(keys, replaceExistingValues, true);
      if (keysToLoad.isEmpty()) {
         setListenerCompletion(listener);
         return;
      }

      try {
         // Using a cyclic barrier, initialised with the number of keys to load,
         // in order to load all keys asynchronously and when the last one completes,
         // callback to the CompletionListener (via a barrier action).
         final CyclicBarrier barrier = new CyclicBarrier(keysToLoad.size(), new Runnable() {
            @Override
            public void run() {
               if (trace)
                  log.tracef("Keys %s loaded, notify listener on completion", keysToLoad);

               setListenerCompletion(listener);
            }
         });
         FutureListener futureListener = new FutureListener() {
            @Override
            public void futureDone(Future future) {
               try {
                  if (trace)
                     log.tracef("Key loaded, wait for the rest of keys to load");

                  barrier.await(30, TimeUnit.SECONDS);
               } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
               } catch (BrokenBarrierException e) {
                  setListenerException(listener, e);
               } catch (TimeoutException e) {
                  setListenerException(listener, e);
               }
            }
         };
         AsyncCache asyncCache = cache;
         for (K k : keysToLoad)
            asyncCache.getAsync(k).attachListener(futureListener);

      } catch (Throwable t) {
         log.errorLoadingAll(keysToLoad, t);
         setListenerException(listener, t);
      }
   }

   private class WithProcessorLock {
      public V call(K key, Callable callable) {
         try {
            acquiredProcessorLock(key);
            return callable.call();
         } catch (InterruptedException e) {
            // restore interrupted status
            Thread.currentThread().interrupt();
            return null;
         } catch (CacheListenerException e) {
            throw Exceptions.launderCacheListenerException(e);
         } catch (EntryProcessorException e) {
            throw e;
         } catch (CacheException e) {
            throw e;
         } catch (Exception e) {
            throw new EntryProcessorException(e);
         } finally {
            releaseProcessorLock(key);
         }
      }
   }

   private class Itr implements Iterator> {

      private final Iterator> it = cache.entrySet().iterator();
      private Entry current;
      private Entry next;

      Itr() {
         fetchNext();
      }

      private void fetchNext() {
         long start = statisticsEnabled() ? System.nanoTime() : 0;
         if (it.hasNext()) {
            Map.Entry entry = it.next();
            next = new JCacheEntry(
                  entry.getKey(), entry.getValue());

            if (statisticsEnabled()) {
               stats.increaseCacheHits(1);
               stats.addGetTimeNano(System.nanoTime() - start);
            }
         } else {
            next = null;
         }
      }

      @Override
      public boolean hasNext() {
         return next != null;
      }

      @Override
      public Entry next() {
         if (next == null)
            fetchNext();

         if (next == null)
            throw new NoSuchElementException();

         // Set return value
         Entry ret = next;

         // Force expiration if needed
         updateTTLForAccessed(cache, next.getKey(), next.getValue());

         current = next;

         // Fetch next...
         fetchNext();

         return ret;
      }

      @Override
      public void remove() {
         if (current == null)
            throw new IllegalStateException();

         // TODO: Should Infinispan's core iterators be mutable?
         // It can be worked around as shown here for JSR-107 needs
         K k = current.getKey();
         current = null;
         cache.remove(k);
      }
   }

   @Override
   protected void addListener(AbstractJCacheListenerAdapter listenerAdapter) {
      cache.addListener(listenerAdapter);
   }

   @Override
   protected void removeListener(AbstractJCacheListenerAdapter listenerAdapter) {
      cache.removeListener(listenerAdapter);
   }

   @Override
   protected void evict(K key) {
      cache.evict(key);
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy