JCSCache.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *   http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.jcs3.jcache;

  20. import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
  21. import static org.apache.commons.jcs3.jcache.serialization.Serializations.copy;

  22. import java.io.Closeable;
  23. import java.io.IOException;
  24. import java.util.Collections;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import java.util.Map;
  29. import java.util.Properties;
  30. import java.util.Set;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import java.util.concurrent.ConcurrentMap;
  33. import java.util.concurrent.ExecutorService;
  34. import java.util.concurrent.Executors;

  35. import javax.cache.Cache;
  36. import javax.cache.CacheException;
  37. import javax.cache.CacheManager;
  38. import javax.cache.configuration.CacheEntryListenerConfiguration;
  39. import javax.cache.configuration.Configuration;
  40. import javax.cache.configuration.Factory;
  41. import javax.cache.event.EventType;
  42. import javax.cache.expiry.Duration;
  43. import javax.cache.expiry.EternalExpiryPolicy;
  44. import javax.cache.expiry.ExpiryPolicy;
  45. import javax.cache.integration.CacheLoader;
  46. import javax.cache.integration.CacheLoaderException;
  47. import javax.cache.integration.CacheWriter;
  48. import javax.cache.integration.CacheWriterException;
  49. import javax.cache.integration.CompletionListener;
  50. import javax.cache.processor.EntryProcessor;
  51. import javax.cache.processor.EntryProcessorException;
  52. import javax.cache.processor.EntryProcessorResult;
  53. import javax.management.ObjectName;

  54. import org.apache.commons.jcs3.engine.CacheElement;
  55. import org.apache.commons.jcs3.engine.ElementAttributes;
  56. import org.apache.commons.jcs3.engine.behavior.ICacheElement;
  57. import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
  58. import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
  59. import org.apache.commons.jcs3.jcache.jmx.JCSCacheMXBean;
  60. import org.apache.commons.jcs3.jcache.jmx.JCSCacheStatisticsMXBean;
  61. import org.apache.commons.jcs3.jcache.jmx.JMXs;
  62. import org.apache.commons.jcs3.jcache.proxy.ExceptionWrapperHandler;
  63. import org.apache.commons.jcs3.jcache.thread.DaemonThreadFactory;
  64. import org.apache.commons.jcs3.utils.serialization.StandardSerializer;

  65. // TODO: configure serializer
  66. public class JCSCache<K, V> implements Cache<K, V>
  67. {
  68.     private final ExpiryAwareCache<K, V> delegate;
  69.     private final JCSCachingManager manager;
  70.     private final JCSConfiguration<K, V> config;
  71.     private final CacheLoader<K, V> loader;
  72.     private final CacheWriter<? super K, ? super V> writer;
  73.     private final ExpiryPolicy expiryPolicy;
  74.     private final ObjectName cacheConfigObjectName;
  75.     private final ObjectName cacheStatsObjectName;
  76.     private final String name;
  77.     private volatile boolean closed;
  78.     private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<>();
  79.     private final Statistics statistics = new Statistics();
  80.     private final ExecutorService pool;
  81.     private final IElementSerializer serializer; // using json/xml should work as well -> don't force Serializable


  82.     public JCSCache(final ClassLoader classLoader, final JCSCachingManager mgr,
  83.                     final String cacheName, final JCSConfiguration<K, V> configuration,
  84.                     final Properties properties, final ExpiryAwareCache<K, V> cache)
  85.     {
  86.         manager = mgr;

  87.         name = cacheName;

  88.         delegate = cache;
  89.         if (delegate.getElementAttributes() == null)
  90.         {
  91.             delegate.setElementAttributes(new ElementAttributes());
  92.         }
  93.         delegate.getElementAttributes().addElementEventHandler(new EvictionListener(statistics));

  94.         config = configuration;

  95.         final int poolSize = Integer.parseInt(property(properties, cacheName, "pool.size", "3"));
  96.         final DaemonThreadFactory threadFactory = new DaemonThreadFactory("JCS-JCache-" + cacheName + "-");
  97.         pool = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory) : Executors.newCachedThreadPool(threadFactory);

  98.         try
  99.         {
  100.             serializer = (IElementSerializer) classLoader.loadClass(property(properties, "serializer", cacheName, StandardSerializer.class.getName())).getDeclaredConstructor().newInstance();
  101.         }
  102.         catch (final Exception e)
  103.         {
  104.             throw new IllegalArgumentException(e);
  105.         }

  106.         final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
  107.         if (cacheLoaderFactory == null)
  108.         {
  109.             loader = (CacheLoader<K, V>) NoLoader.INSTANCE;
  110.         }
  111.         else
  112.         {
  113.             loader = ExceptionWrapperHandler
  114.                     .newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class, CacheLoader.class);
  115.         }

  116.         final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
  117.         if (cacheWriterFactory == null)
  118.         {
  119.             writer = (CacheWriter<K, V>) NoWriter.INSTANCE;
  120.         }
  121.         else
  122.         {
  123.             writer = ExceptionWrapperHandler
  124.                     .newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class, CacheWriter.class);
  125.         }

  126.         final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
  127.         if (expiryPolicyFactory == null)
  128.         {
  129.             expiryPolicy = new EternalExpiryPolicy();
  130.         }
  131.         else
  132.         {
  133.             expiryPolicy = expiryPolicyFactory.create();
  134.         }

  135.         for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations())
  136.         {
  137.             listeners.put(listener, new JCSListener<>(listener));
  138.         }
  139.         delegate.init(this, listeners);

  140.         statistics.setActive(config.isStatisticsEnabled());

  141.         final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
  142.         final String cacheStr = name.replaceAll(",|:|=|\n", ".");
  143.         try
  144.         {
  145.             cacheConfigObjectName = new ObjectName("javax.cache:type=CacheConfiguration,"
  146.                     + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
  147.             cacheStatsObjectName = new ObjectName("javax.cache:type=CacheStatistics,"
  148.                     + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
  149.         }
  150.         catch (final Exception e)
  151.         {
  152.             throw new IllegalArgumentException(e);
  153.         }
  154.         if (config.isManagementEnabled())
  155.         {
  156.             JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
  157.         }
  158.         if (config.isStatisticsEnabled())
  159.         {
  160.             JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
  161.         }
  162.     }

  163.     private static String property(final Properties properties, final String cacheName, final String name, final String defaultValue)
  164.     {
  165.         return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
  166.     }

  167.     private void assertNotClosed()
  168.     {
  169.         if (isClosed())
  170.         {
  171.             throw new IllegalStateException("cache closed");
  172.         }
  173.     }

  174.     @Override
  175.     public V get(final K key)
  176.     {
  177.         assertNotClosed();
  178.         assertNotNull(key, "key");
  179.         final long getStart = Times.now(false);
  180.         return doGetControllingExpiry(getStart, key, true, false, false, true);
  181.     }

  182.     private V doLoad(final K key, final boolean update, final long now, final boolean propagateLoadException)
  183.     {
  184.         V v = null;
  185.         try
  186.         {
  187.             v = loader.load(key);
  188.         }
  189.         catch (final CacheLoaderException e)
  190.         {
  191.             if (propagateLoadException)
  192.             {
  193.                 throw e;
  194.             }
  195.         }
  196.         if (v != null)
  197.         {
  198.             final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
  199.             if (isNotZero(duration))
  200.             {
  201.                 final IElementAttributes clone = delegate.getElementAttributes().clone();
  202.                 if (ElementAttributes.class.isInstance(clone))
  203.                 {
  204.                     ElementAttributes.class.cast(clone).setCreateTime();
  205.                 }
  206.                 final ICacheElement<K, V> element = updateElement(key, v, duration, clone);
  207.                 try
  208.                 {
  209.                     delegate.update(element);
  210.                 }
  211.                 catch (final IOException e)
  212.                 {
  213.                     throw new CacheException(e);
  214.                 }
  215.             }
  216.         }
  217.         return v;
  218.     }

  219.     private ICacheElement<K, V> updateElement(final K key, final V v, final Duration duration, final IElementAttributes attrs)
  220.     {
  221.         final ICacheElement<K, V> element = new CacheElement<>(name, key, v);
  222.         if (duration != null)
  223.         {
  224.             attrs.setTimeFactorForMilliseconds(1);
  225.             final boolean eternal = duration.isEternal();
  226.             attrs.setIsEternal(eternal);
  227.             if (!eternal)
  228.             {
  229.                 attrs.setLastAccessTimeNow();
  230.             }
  231.             // MaxLife = -1 to use IdleTime excepted if jcache.ccf asked for something else
  232.         }
  233.         element.setElementAttributes(attrs);
  234.         return element;
  235.     }

  236.     private void touch(final K key, final ICacheElement<K, V> element)
  237.     {
  238.         if (config.isStoreByValue())
  239.         {
  240.             final K copy = copy(serializer, manager.getClassLoader(), key);
  241.             try
  242.             {
  243.                 delegate.update(new CacheElement<>(name, copy, element.getVal(), element.getElementAttributes()));
  244.             }
  245.             catch (final IOException e)
  246.             {
  247.                 throw new CacheException(e);
  248.             }
  249.         }
  250.     }

  251.     @Override
  252.     public Map<K, V> getAll(final Set<? extends K> keys)
  253.     {
  254.         assertNotClosed();
  255.         for (final K k : keys)
  256.         {
  257.             assertNotNull(k, "key");
  258.         }

  259.         final long now = Times.now(false);
  260.         final Map<K, V> result = new HashMap<>();
  261.         for (final K key : keys) {
  262.             assertNotNull(key, "key");

  263.             final ICacheElement<K, V> elt = delegate.get(key);
  264.             V val = elt != null ? elt.getVal() : null;
  265.             if (val == null && config.isReadThrough())
  266.             {
  267.                 val = doLoad(key, false, now, false);
  268.                 if (val != null)
  269.                 {
  270.                     result.put(key, val);
  271.                 }
  272.             }
  273.             else if (elt != null)
  274.             {
  275.                 final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
  276.                 if (isNotZero(expiryForAccess))
  277.                 {
  278.                     touch(key, elt);
  279.                     result.put(key, val);
  280.                 }
  281.                 else
  282.                 {
  283.                     forceExpires(key);
  284.                 }
  285.             }
  286.         }
  287.         return result;
  288.     }

  289.     @Override
  290.     public boolean containsKey(final K key)
  291.     {
  292.         assertNotClosed();
  293.         assertNotNull(key, "key");
  294.         return delegate.get(key) != null;
  295.     }

  296.     @Override
  297.     public void put(final K key, final V rawValue)
  298.     {
  299.         assertNotClosed();
  300.         assertNotNull(key, "key");
  301.         assertNotNull(rawValue, "value");

  302.         final ICacheElement<K, V> oldElt = delegate.get(key);
  303.         final V old = oldElt != null ? oldElt.getVal() : null;

  304.         final boolean storeByValue = config.isStoreByValue();
  305.         final V value = storeByValue ? copy(serializer, manager.getClassLoader(), rawValue) : rawValue;

  306.         final boolean created = old == null;
  307.         final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
  308.         if (isNotZero(duration))
  309.         {
  310.             final boolean statisticsEnabled = config.isStatisticsEnabled();
  311.             final long start = Times.now(false);

  312.             final K jcsKey = storeByValue ? copy(serializer, manager.getClassLoader(), key) : key;
  313.             final ICacheElement<K, V> element = updateElement( // reuse it to create basic structure
  314.                     jcsKey, value, created ? null : duration,
  315.                     oldElt != null ? oldElt.getElementAttributes() : delegate.getElementAttributes().clone());
  316.             if (created && duration != null) { // set maxLife
  317.                 final IElementAttributes copy = element.getElementAttributes();
  318.                 copy.setTimeFactorForMilliseconds(1);
  319.                 final boolean eternal = duration.isEternal();
  320.                 copy.setIsEternal(eternal);
  321.                 if (ElementAttributes.class.isInstance(copy)) {
  322.                     ElementAttributes.class.cast(copy).setCreateTime();
  323.                 }
  324.                 if (!eternal)
  325.                 {
  326.                     copy.setIsEternal(false);
  327.                     if (duration == expiryPolicy.getExpiryForAccess())
  328.                     {
  329.                         element.getElementAttributes().setIdleTime(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
  330.                     }
  331.                     else
  332.                         {
  333.                         element.getElementAttributes().setMaxLife(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
  334.                     }
  335.                 }
  336.                 element.setElementAttributes(copy);
  337.             }
  338.             writer.write(new JCSEntry<>(jcsKey, value));
  339.             try
  340.             {
  341.                 delegate.update(element);
  342.             }
  343.             catch (final IOException e)
  344.             {
  345.                 throw new CacheException(e);
  346.             }
  347.             for (final JCSListener<K, V> listener : listeners.values())
  348.             {
  349.                 if (created)
  350.                 {
  351.                     listener.onCreated(Collections.singletonList(new JCSCacheEntryEvent<>(this,
  352.                             EventType.CREATED, null, key, value)));
  353.                 }
  354.                 else
  355.                 {
  356.                     listener.onUpdated(Collections.singletonList(new JCSCacheEntryEvent<>(this,
  357.                             EventType.UPDATED, old, key, value)));
  358.                 }
  359.             }

  360.             if (statisticsEnabled)
  361.             {
  362.                 statistics.increasePuts(1);
  363.                 statistics.addPutTime(System.currentTimeMillis() - start);
  364.             }
  365.         }
  366.         else
  367.         {
  368.             if (!created)
  369.             {
  370.                 forceExpires(key);
  371.             }
  372.         }
  373.     }

  374.     private static boolean isNotZero(final Duration duration)
  375.     {
  376.         return duration == null || !duration.isZero();
  377.     }

  378.     private void forceExpires(final K cacheKey)
  379.     {
  380.         final ICacheElement<K, V> elt = delegate.get(cacheKey);
  381.         delegate.remove(cacheKey);
  382.         for (final JCSListener<K, V> listener : listeners.values())
  383.         {
  384.             listener.onExpired(Collections.singletonList(new JCSCacheEntryEvent<>(this,
  385.                     EventType.REMOVED, null, cacheKey, elt.getVal())));
  386.         }
  387.     }

  388.     @Override
  389.     public V getAndPut(final K key, final V value)
  390.     {
  391.         assertNotClosed();
  392.         assertNotNull(key, "key");
  393.         assertNotNull(value, "value");
  394.         final long getStart = Times.now(false);
  395.         final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
  396.         put(key, value);
  397.         return v;
  398.     }

  399.     @Override
  400.     public void putAll(final Map<? extends K, ? extends V> map)
  401.     {
  402.         assertNotClosed();
  403.         final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
  404.         for (final Map.Entry<? extends K, ? extends V> e : map.entrySet())
  405.         {
  406.             view.put(e.getKey(), e.getValue());
  407.         }
  408.         view.merge();
  409.     }

  410.     @Override
  411.     public boolean putIfAbsent(final K key, final V value)
  412.     {
  413.         if (!containsKey(key))
  414.         {
  415.             put(key, value);
  416.             return true;
  417.         }
  418.         return false;
  419.     }

  420.     @Override
  421.     public boolean remove(final K key)
  422.     {
  423.         assertNotClosed();
  424.         assertNotNull(key, "key");

  425.         final boolean statisticsEnabled = config.isStatisticsEnabled();
  426.         final long start = Times.now(!statisticsEnabled);

  427.         writer.delete(key);

  428.         final ICacheElement<K, V> v = delegate.get(key);
  429.         delegate.remove(key);

  430.         final V value = v != null && v.getVal() != null ? v.getVal() : null;
  431.         final boolean remove = v != null;
  432.         for (final JCSListener<K, V> listener : listeners.values())
  433.         {
  434.             listener.onRemoved(Collections.singletonList(new JCSCacheEntryEvent<>(this,
  435.                     EventType.REMOVED, null, key, value)));
  436.         }
  437.         if (remove && statisticsEnabled)
  438.         {
  439.             statistics.increaseRemovals(1);
  440.             statistics.addRemoveTime(Times.now(false) - start);
  441.         }
  442.         return remove;
  443.     }

  444.     @Override
  445.     public boolean remove(final K key, final V oldValue)
  446.     {
  447.         assertNotClosed();
  448.         assertNotNull(key, "key");
  449.         assertNotNull(oldValue, "oldValue");
  450.         final long getStart = Times.now(false);
  451.         final V v = doGetControllingExpiry(getStart, key, false, false, false, false);
  452.         if (oldValue.equals(v))
  453.         {
  454.             remove(key);
  455.             return true;
  456.         }
  457.         if (v != null)
  458.         {
  459.             // weird but just for stats to be right (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
  460.             expiryPolicy.getExpiryForAccess();
  461.         }
  462.         return false;
  463.     }

  464.     @Override
  465.     public V getAndRemove(final K key)
  466.     {
  467.         assertNotClosed();
  468.         assertNotNull(key, "key");
  469.         final long getStart = Times.now(false);
  470.         final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
  471.         remove(key);
  472.         return v;
  473.     }

  474.     private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, final boolean skipLoad,
  475.             final boolean propagateLoadException)
  476.     {
  477.         final boolean statisticsEnabled = config.isStatisticsEnabled();
  478.         final ICacheElement<K, V> elt = delegate.get(key);
  479.         V v = elt != null ? elt.getVal() : null;
  480.         if (v == null && (config.isReadThrough() || forceDoLoad))
  481.         {
  482.             if (!skipLoad)
  483.             {
  484.                 v = doLoad(key, false, getStart, propagateLoadException);
  485.             }
  486.         }
  487.         else if (statisticsEnabled)
  488.         {
  489.             if (v != null)
  490.             {
  491.                 statistics.increaseHits(1);
  492.             }
  493.             else
  494.             {
  495.                 statistics.increaseMisses(1);
  496.             }
  497.         }

  498.         if (updateAcess && elt != null)
  499.         {
  500.             final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
  501.             if (!isNotZero(expiryForAccess))
  502.             {
  503.                 forceExpires(key);
  504.             }
  505.             else if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
  506.             {
  507.                 try
  508.                 {
  509.                     delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
  510.                 }
  511.                 catch (final IOException e)
  512.                 {
  513.                     throw new CacheException(e);
  514.                 }
  515.             }
  516.         }
  517.         if (statisticsEnabled && v != null)
  518.         {
  519.             statistics.addGetTime(Times.now(false) - getStart);
  520.         }
  521.         return v;
  522.     }

  523.     @Override
  524.     public boolean replace(final K key, final V oldValue, final V newValue)
  525.     {
  526.         assertNotClosed();
  527.         assertNotNull(key, "key");
  528.         assertNotNull(oldValue, "oldValue");
  529.         assertNotNull(newValue, "newValue");
  530.         final boolean statisticsEnabled = config.isStatisticsEnabled();
  531.         final ICacheElement<K, V> elt = delegate.get(key);
  532.         if (elt != null)
  533.         {
  534.             V value = elt.getVal();
  535.             if (value != null && statisticsEnabled)
  536.             {
  537.                 statistics.increaseHits(1);
  538.             }
  539.             if (value == null && config.isReadThrough())
  540.             {
  541.                 value = doLoad(key, false, Times.now(false), false);
  542.             }
  543.             if (value != null && value.equals(oldValue))
  544.             {
  545.                 put(key, newValue);
  546.                 return true;
  547.             }
  548.             if (value != null)
  549.             {
  550.                 final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
  551.                 if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
  552.                 {
  553.                     try
  554.                     {
  555.                         delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
  556.                     }
  557.                     catch (final IOException e)
  558.                     {
  559.                         throw new CacheException(e);
  560.                     }
  561.                 }
  562.             }
  563.         }
  564.         else if (statisticsEnabled)
  565.         {
  566.             statistics.increaseMisses(1);
  567.         }
  568.         return false;
  569.     }

  570.     @Override
  571.     public boolean replace(final K key, final V value)
  572.     {
  573.         assertNotClosed();
  574.         assertNotNull(key, "key");
  575.         assertNotNull(value, "value");
  576.         final boolean statisticsEnabled = config.isStatisticsEnabled();
  577.         if (containsKey(key))
  578.         {
  579.             if (statisticsEnabled)
  580.             {
  581.                 statistics.increaseHits(1);
  582.             }
  583.             put(key, value);
  584.             return true;
  585.         }
  586.         if (statisticsEnabled)
  587.         {
  588.             statistics.increaseMisses(1);
  589.         }
  590.         return false;
  591.     }

  592.     @Override
  593.     public V getAndReplace(final K key, final V value)
  594.     {
  595.         assertNotClosed();
  596.         assertNotNull(key, "key");
  597.         assertNotNull(value, "value");

  598.         final boolean statisticsEnabled = config.isStatisticsEnabled();

  599.         final ICacheElement<K, V> elt = delegate.get(key);
  600.         if (elt != null)
  601.         {
  602.             V oldValue = elt.getVal();
  603.             if (oldValue == null && config.isReadThrough())
  604.             {
  605.                 oldValue = doLoad(key, false, Times.now(false), false);
  606.             }
  607.             else if (statisticsEnabled)
  608.             {
  609.                 statistics.increaseHits(1);
  610.             }
  611.             put(key, value);
  612.             return oldValue;
  613.         }
  614.         if (statisticsEnabled)
  615.         {
  616.             statistics.increaseMisses(1);
  617.         }
  618.         return null;
  619.     }

  620.     @Override
  621.     public void removeAll(final Set<? extends K> keys)
  622.     {
  623.         assertNotClosed();
  624.         assertNotNull(keys, "keys");
  625.         for (final K k : keys)
  626.         {
  627.             remove(k);
  628.         }
  629.     }

  630.     @Override
  631.     public void removeAll()
  632.     {
  633.         assertNotClosed();
  634.         for (final K k : delegate.getKeySet())
  635.         {
  636.             remove(k);
  637.         }
  638.     }

  639.     @Override
  640.     public void clear()
  641.     {
  642.         assertNotClosed();
  643.         try
  644.         {
  645.             delegate.removeAll();
  646.         }
  647.         catch (final IOException e)
  648.         {
  649.             throw new CacheException(e);
  650.         }
  651.     }

  652.     @Override
  653.     public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz)
  654.     {
  655.         assertNotClosed();
  656.         return clazz.cast(config);
  657.     }

  658.     @Override
  659.     public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
  660.     {
  661.         assertNotClosed();
  662.         assertNotNull(keys, "keys");
  663.         for (final K k : keys)
  664.         {
  665.             assertNotNull(k, "a key");
  666.         }
  667.         pool.submit(() -> doLoadAll(keys, replaceExistingValues, completionListener));
  668.     }

  669.     private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
  670.     {
  671.         try
  672.         {
  673.             final long now = Times.now(false);
  674.             for (final K k : keys)
  675.             {
  676.                 if (replaceExistingValues)
  677.                 {
  678.                     doLoad(k, containsKey(k), now, completionListener != null);
  679.                     continue;
  680.                 }
  681.                 if (containsKey(k))
  682.                 {
  683.                     continue;
  684.                 }
  685.                 doGetControllingExpiry(now, k, true, true, false, completionListener != null);
  686.             }
  687.         }
  688.         catch (final RuntimeException e)
  689.         {
  690.             if (completionListener != null)
  691.             {
  692.                 completionListener.onException(e);
  693.                 return;
  694.             }
  695.         }
  696.         if (completionListener != null)
  697.         {
  698.             completionListener.onCompletion();
  699.         }
  700.     }

  701.     @Override
  702.     public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
  703.     {
  704.         final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
  705.         final T t = doInvoke(view, key, entryProcessor, arguments);
  706.         view.merge();
  707.         return t;
  708.     }

  709.     private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
  710.             final Object... arguments)
  711.     {
  712.         assertNotClosed();
  713.         assertNotNull(entryProcessor, "entryProcessor");
  714.         assertNotNull(key, "key");
  715.         try
  716.         {
  717.             if (config.isStatisticsEnabled())
  718.             {
  719.                 if (containsKey(key))
  720.                 {
  721.                     statistics.increaseHits(1);
  722.                 }
  723.                 else
  724.                 {
  725.                     statistics.increaseMisses(1);
  726.                 }
  727.             }
  728.             return entryProcessor.process(new JCSMutableEntry<>(view, key), arguments);
  729.         }
  730.         catch (final Exception ex)
  731.         {
  732.             return throwEntryProcessorException(ex);
  733.         }
  734.     }

  735.     private static <T> T throwEntryProcessorException(final Exception ex)
  736.     {
  737.         if (EntryProcessorException.class.isInstance(ex))
  738.         {
  739.             throw EntryProcessorException.class.cast(ex);
  740.         }
  741.         throw new EntryProcessorException(ex);
  742.     }

  743.     @Override
  744.     public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
  745.             final Object... arguments)
  746.     {
  747.         assertNotClosed();
  748.         assertNotNull(entryProcessor, "entryProcessor");
  749.         final Map<K, EntryProcessorResult<T>> results = new HashMap<>();
  750.         for (final K k : keys)
  751.         {
  752.             try
  753.             {
  754.                 final T invoke = invoke(k, entryProcessor, arguments);
  755.                 if (invoke != null)
  756.                 {
  757.                     results.put(k, () -> invoke);
  758.                 }
  759.             }
  760.             catch (final Exception e)
  761.             {
  762.                 results.put(k, () -> throwEntryProcessorException(e));
  763.             }
  764.         }
  765.         return results;
  766.     }

  767.     @Override
  768.     public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
  769.     {
  770.         assertNotClosed();
  771.         if (listeners.containsKey(cacheEntryListenerConfiguration))
  772.         {
  773.             throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
  774.         }
  775.         listeners.put(cacheEntryListenerConfiguration, new JCSListener<>(cacheEntryListenerConfiguration));
  776.         config.addListener(cacheEntryListenerConfiguration);
  777.     }

  778.     @Override
  779.     public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
  780.     {
  781.         assertNotClosed();
  782.         listeners.remove(cacheEntryListenerConfiguration);
  783.         config.removeListener(cacheEntryListenerConfiguration);
  784.     }

  785.     @Override
  786.     public Iterator<Entry<K, V>> iterator()
  787.     {
  788.         assertNotClosed();
  789.         final Iterator<K> keys = new HashSet<>(delegate.getKeySet()).iterator();
  790.         return new Iterator<Entry<K, V>>()
  791.         {
  792.             private K lastKey;

  793.             @Override
  794.             public boolean hasNext()
  795.             {
  796.                 return keys.hasNext();
  797.             }

  798.             @Override
  799.             public Entry<K, V> next()
  800.             {
  801.                 lastKey = keys.next();
  802.                 return new JCSEntry<>(lastKey, get(lastKey));
  803.             }

  804.             @Override
  805.             public void remove()
  806.             {
  807.                 if (isClosed() || lastKey == null)
  808.                 {
  809.                     throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
  810.                 }
  811.                 JCSCache.this.remove(lastKey);
  812.             }
  813.         };
  814.     }

  815.     @Override
  816.     public String getName()
  817.     {
  818.         assertNotClosed();
  819.         return name;
  820.     }

  821.     @Override
  822.     public CacheManager getCacheManager()
  823.     {
  824.         assertNotClosed();
  825.         return manager;
  826.     }

  827.     @Override
  828.     public synchronized void close()
  829.     {
  830.         if (isClosed())
  831.         {
  832.             return;
  833.         }

  834.         for (final Runnable task : pool.shutdownNow()) {
  835.             task.run();
  836.         }

  837.         manager.release(getName());
  838.         closed = true;
  839.         close(loader);
  840.         close(writer);
  841.         close(expiryPolicy);
  842.         for (final JCSListener<K, V> listener : listeners.values())
  843.         {
  844.             close(listener);
  845.         }
  846.         listeners.clear();
  847.         JMXs.unregister(cacheConfigObjectName);
  848.         JMXs.unregister(cacheStatsObjectName);
  849.         try
  850.         {
  851.             delegate.removeAll();
  852.         }
  853.         catch (final IOException e)
  854.         {
  855.             throw new CacheException(e);
  856.         }
  857.     }

  858.     private static void close(final Object potentiallyCloseable)
  859.     {
  860.         if (Closeable.class.isInstance(potentiallyCloseable))
  861.         {
  862.             Closeable.class.cast(potentiallyCloseable);
  863.         }
  864.     }

  865.     @Override
  866.     public boolean isClosed()
  867.     {
  868.         return closed;
  869.     }

  870.     @Override
  871.     public <T> T unwrap(final Class<T> clazz)
  872.     {
  873.         assertNotClosed();
  874.         if (clazz.isInstance(this))
  875.         {
  876.             return clazz.cast(this);
  877.         }
  878.         if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class))
  879.         {
  880.             return clazz.cast(delegate);
  881.         }
  882.         throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
  883.     }

  884.     public Statistics getStatistics()
  885.     {
  886.         return statistics;
  887.     }

  888.     public void enableManagement()
  889.     {
  890.         config.managementEnabled();
  891.         JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
  892.     }

  893.     public void disableManagement()
  894.     {
  895.         config.managementDisabled();
  896.         JMXs.unregister(cacheConfigObjectName);
  897.     }

  898.     public void enableStatistics()
  899.     {
  900.         config.statisticsEnabled();
  901.         statistics.setActive(true);
  902.         JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
  903.     }

  904.     public void disableStatistics()
  905.     {
  906.         config.statisticsDisabled();
  907.         statistics.setActive(false);
  908.         JMXs.unregister(cacheStatsObjectName);
  909.     }
  910. }