View Javadoc
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  
21  import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
22  import static org.apache.commons.jcs3.jcache.serialization.Serializations.copy;
23  
24  import java.io.Closeable;
25  import java.io.IOException;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ConcurrentMap;
35  import java.util.concurrent.ExecutorService;
36  import java.util.concurrent.Executors;
37  
38  import javax.cache.Cache;
39  import javax.cache.CacheException;
40  import javax.cache.CacheManager;
41  import javax.cache.configuration.CacheEntryListenerConfiguration;
42  import javax.cache.configuration.Configuration;
43  import javax.cache.configuration.Factory;
44  import javax.cache.event.EventType;
45  import javax.cache.expiry.Duration;
46  import javax.cache.expiry.EternalExpiryPolicy;
47  import javax.cache.expiry.ExpiryPolicy;
48  import javax.cache.integration.CacheLoader;
49  import javax.cache.integration.CacheLoaderException;
50  import javax.cache.integration.CacheWriter;
51  import javax.cache.integration.CacheWriterException;
52  import javax.cache.integration.CompletionListener;
53  import javax.cache.processor.EntryProcessor;
54  import javax.cache.processor.EntryProcessorException;
55  import javax.cache.processor.EntryProcessorResult;
56  import javax.management.ObjectName;
57  
58  import org.apache.commons.jcs3.engine.CacheElement;
59  import org.apache.commons.jcs3.engine.ElementAttributes;
60  import org.apache.commons.jcs3.engine.behavior.ICacheElement;
61  import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
62  import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
63  import org.apache.commons.jcs3.jcache.jmx.JCSCacheMXBean;
64  import org.apache.commons.jcs3.jcache.jmx.JCSCacheStatisticsMXBean;
65  import org.apache.commons.jcs3.jcache.jmx.JMXs;
66  import org.apache.commons.jcs3.jcache.proxy.ExceptionWrapperHandler;
67  import org.apache.commons.jcs3.jcache.thread.DaemonThreadFactory;
68  import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
69  
70  // TODO: configure serializer
71  public class JCSCache<K, V> implements Cache<K, V>
72  {
73      private final ExpiryAwareCache<K, V> delegate;
74      private final JCSCachingManager manager;
75      private final JCSConfiguration<K, V> config;
76      private final CacheLoader<K, V> loader;
77      private final CacheWriter<? super K, ? super V> writer;
78      private final ExpiryPolicy expiryPolicy;
79      private final ObjectName cacheConfigObjectName;
80      private final ObjectName cacheStatsObjectName;
81      private final String name;
82      private volatile boolean closed;
83      private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<>();
84      private final Statistics statistics = new Statistics();
85      private final ExecutorService pool;
86      private final IElementSerializer serializer; // using json/xml should work as well -> don't force Serializable
87  
88  
89      public JCSCache(final ClassLoader classLoader, final JCSCachingManager mgr,
90                      final String cacheName, final JCSConfiguration<K, V> configuration,
91                      final Properties properties, final ExpiryAwareCache<K, V> cache)
92      {
93          manager = mgr;
94  
95          name = cacheName;
96  
97          delegate = cache;
98          if (delegate.getElementAttributes() == null)
99          {
100             delegate.setElementAttributes(new ElementAttributes());
101         }
102         delegate.getElementAttributes().addElementEventHandler(new EvictionListener(statistics));
103 
104         config = configuration;
105 
106         final int poolSize = Integer.parseInt(property(properties, cacheName, "pool.size", "3"));
107         final DaemonThreadFactory threadFactory = new DaemonThreadFactory("JCS-JCache-" + cacheName + "-");
108         pool = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory) : Executors.newCachedThreadPool(threadFactory);
109 
110         try
111         {
112             serializer = (IElementSerializer) classLoader.loadClass(property(properties, "serializer", cacheName, StandardSerializer.class.getName())).getDeclaredConstructor().newInstance();
113         }
114         catch (final Exception e)
115         {
116             throw new IllegalArgumentException(e);
117         }
118 
119         final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
120         if (cacheLoaderFactory == null)
121         {
122             loader = (CacheLoader<K, V>) NoLoader.INSTANCE;
123         }
124         else
125         {
126             loader = ExceptionWrapperHandler
127                     .newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class, CacheLoader.class);
128         }
129 
130         final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
131         if (cacheWriterFactory == null)
132         {
133             writer = (CacheWriter<K, V>) NoWriter.INSTANCE;
134         }
135         else
136         {
137             writer = ExceptionWrapperHandler
138                     .newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class, CacheWriter.class);
139         }
140 
141         final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
142         if (expiryPolicyFactory == null)
143         {
144             expiryPolicy = new EternalExpiryPolicy();
145         }
146         else
147         {
148             expiryPolicy = expiryPolicyFactory.create();
149         }
150 
151         for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations())
152         {
153             listeners.put(listener, new JCSListener<>(listener));
154         }
155         delegate.init(this, listeners);
156 
157         statistics.setActive(config.isStatisticsEnabled());
158 
159         final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
160         final String cacheStr = name.replaceAll(",|:|=|\n", ".");
161         try
162         {
163             cacheConfigObjectName = new ObjectName("javax.cache:type=CacheConfiguration,"
164                     + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
165             cacheStatsObjectName = new ObjectName("javax.cache:type=CacheStatistics,"
166                     + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
167         }
168         catch (final Exception e)
169         {
170             throw new IllegalArgumentException(e);
171         }
172         if (config.isManagementEnabled())
173         {
174             JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
175         }
176         if (config.isStatisticsEnabled())
177         {
178             JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
179         }
180     }
181 
182     private static String property(final Properties properties, final String cacheName, final String name, final String defaultValue)
183     {
184         return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
185     }
186 
187     private void assertNotClosed()
188     {
189         if (isClosed())
190         {
191             throw new IllegalStateException("cache closed");
192         }
193     }
194 
195     @Override
196     public V get(final K key)
197     {
198         assertNotClosed();
199         assertNotNull(key, "key");
200         final long getStart = Times.now(false);
201         return doGetControllingExpiry(getStart, key, true, false, false, true);
202     }
203 
204     private V doLoad(final K key, final boolean update, final long now, final boolean propagateLoadException)
205     {
206         V v = null;
207         try
208         {
209             v = loader.load(key);
210         }
211         catch (final CacheLoaderException e)
212         {
213             if (propagateLoadException)
214             {
215                 throw e;
216             }
217         }
218         if (v != null)
219         {
220             final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
221             if (isNotZero(duration))
222             {
223                 final IElementAttributes clone = delegate.getElementAttributes().clone();
224                 if (ElementAttributes.class.isInstance(clone))
225                 {
226                     ElementAttributes.class.cast(clone).setCreateTime();
227                 }
228                 final ICacheElement<K, V> element = updateElement(key, v, duration, clone);
229                 try
230                 {
231                     delegate.update(element);
232                 }
233                 catch (final IOException e)
234                 {
235                     throw new CacheException(e);
236                 }
237             }
238         }
239         return v;
240     }
241 
242     private ICacheElement<K, V> updateElement(final K key, final V v, final Duration duration, final IElementAttributes attrs)
243     {
244         final ICacheElement<K, V> element = new CacheElement<>(name, key, v);
245         if (duration != null)
246         {
247             attrs.setTimeFactorForMilliseconds(1);
248             final boolean eternal = duration.isEternal();
249             attrs.setIsEternal(eternal);
250             if (!eternal)
251             {
252                 attrs.setLastAccessTimeNow();
253             }
254             // MaxLife = -1 to use IdleTime excepted if jcache.ccf asked for something else
255         }
256         element.setElementAttributes(attrs);
257         return element;
258     }
259 
260     private void touch(final K key, final ICacheElement<K, V> element)
261     {
262         if (config.isStoreByValue())
263         {
264             final K copy = copy(serializer, manager.getClassLoader(), key);
265             try
266             {
267                 delegate.update(new CacheElement<>(name, copy, element.getVal(), element.getElementAttributes()));
268             }
269             catch (final IOException e)
270             {
271                 throw new CacheException(e);
272             }
273         }
274     }
275 
276     @Override
277     public Map<K, V> getAll(final Set<? extends K> keys)
278     {
279         assertNotClosed();
280         for (final K k : keys)
281         {
282             assertNotNull(k, "key");
283         }
284 
285         final long now = Times.now(false);
286         final Map<K, V> result = new HashMap<>();
287         for (final K key : keys) {
288             assertNotNull(key, "key");
289 
290             final ICacheElement<K, V> elt = delegate.get(key);
291             V val = elt != null ? elt.getVal() : null;
292             if (val == null && config.isReadThrough())
293             {
294                 val = doLoad(key, false, now, false);
295                 if (val != null)
296                 {
297                     result.put(key, val);
298                 }
299             }
300             else if (elt != null)
301             {
302                 final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
303                 if (isNotZero(expiryForAccess))
304                 {
305                     touch(key, elt);
306                     result.put(key, val);
307                 }
308                 else
309                 {
310                     forceExpires(key);
311                 }
312             }
313         }
314         return result;
315     }
316 
317     @Override
318     public boolean containsKey(final K key)
319     {
320         assertNotClosed();
321         assertNotNull(key, "key");
322         return delegate.get(key) != null;
323     }
324 
325     @Override
326     public void put(final K key, final V rawValue)
327     {
328         assertNotClosed();
329         assertNotNull(key, "key");
330         assertNotNull(rawValue, "value");
331 
332         final ICacheElement<K, V> oldElt = delegate.get(key);
333         final V old = oldElt != null ? oldElt.getVal() : null;
334 
335         final boolean storeByValue = config.isStoreByValue();
336         final V value = storeByValue ? copy(serializer, manager.getClassLoader(), rawValue) : rawValue;
337 
338         final boolean created = old == null;
339         final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
340         if (isNotZero(duration))
341         {
342             final boolean statisticsEnabled = config.isStatisticsEnabled();
343             final long start = Times.now(false);
344 
345             final K jcsKey = storeByValue ? copy(serializer, manager.getClassLoader(), key) : key;
346             final ICacheElement<K, V> element = updateElement( // reuse it to create basic structure
347                     jcsKey, value, created ? null : duration,
348                     oldElt != null ? oldElt.getElementAttributes() : delegate.getElementAttributes().clone());
349             if (created && duration != null) { // set maxLife
350                 final IElementAttributes copy = element.getElementAttributes();
351                 copy.setTimeFactorForMilliseconds(1);
352                 final boolean eternal = duration.isEternal();
353                 copy.setIsEternal(eternal);
354                 if (ElementAttributes.class.isInstance(copy)) {
355                     ElementAttributes.class.cast(copy).setCreateTime();
356                 }
357                 if (!eternal)
358                 {
359                     copy.setIsEternal(false);
360                     if (duration == expiryPolicy.getExpiryForAccess())
361                     {
362                         element.getElementAttributes().setIdleTime(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
363                     }
364                     else
365                         {
366                         element.getElementAttributes().setMaxLife(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
367                     }
368                 }
369                 element.setElementAttributes(copy);
370             }
371             writer.write(new JCSEntry<>(jcsKey, value));
372             try
373             {
374                 delegate.update(element);
375             }
376             catch (final IOException e)
377             {
378                 throw new CacheException(e);
379             }
380             for (final JCSListener<K, V> listener : listeners.values())
381             {
382                 if (created)
383                 {
384                     listener.onCreated(Collections.singletonList(new JCSCacheEntryEvent<>(this,
385                             EventType.CREATED, null, key, value)));
386                 }
387                 else
388                 {
389                     listener.onUpdated(Collections.singletonList(new JCSCacheEntryEvent<>(this,
390                             EventType.UPDATED, old, key, value)));
391                 }
392             }
393 
394             if (statisticsEnabled)
395             {
396                 statistics.increasePuts(1);
397                 statistics.addPutTime(System.currentTimeMillis() - start);
398             }
399         }
400         else
401         {
402             if (!created)
403             {
404                 forceExpires(key);
405             }
406         }
407     }
408 
409     private static boolean isNotZero(final Duration duration)
410     {
411         return duration == null || !duration.isZero();
412     }
413 
414     private void forceExpires(final K cacheKey)
415     {
416         final ICacheElement<K, V> elt = delegate.get(cacheKey);
417         delegate.remove(cacheKey);
418         for (final JCSListener<K, V> listener : listeners.values())
419         {
420             listener.onExpired(Collections.singletonList(new JCSCacheEntryEvent<>(this,
421                     EventType.REMOVED, null, cacheKey, elt.getVal())));
422         }
423     }
424 
425     @Override
426     public V getAndPut(final K key, final V value)
427     {
428         assertNotClosed();
429         assertNotNull(key, "key");
430         assertNotNull(value, "value");
431         final long getStart = Times.now(false);
432         final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
433         put(key, value);
434         return v;
435     }
436 
437     @Override
438     public void putAll(final Map<? extends K, ? extends V> map)
439     {
440         assertNotClosed();
441         final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
442         for (final Map.Entry<? extends K, ? extends V> e : map.entrySet())
443         {
444             view.put(e.getKey(), e.getValue());
445         }
446         view.merge();
447     }
448 
449     @Override
450     public boolean putIfAbsent(final K key, final V value)
451     {
452         if (!containsKey(key))
453         {
454             put(key, value);
455             return true;
456         }
457         return false;
458     }
459 
460     @Override
461     public boolean remove(final K key)
462     {
463         assertNotClosed();
464         assertNotNull(key, "key");
465 
466         final boolean statisticsEnabled = config.isStatisticsEnabled();
467         final long start = Times.now(!statisticsEnabled);
468 
469         writer.delete(key);
470 
471         final ICacheElement<K, V> v = delegate.get(key);
472         delegate.remove(key);
473 
474         final V value = v != null && v.getVal() != null ? v.getVal() : null;
475         final boolean remove = v != null;
476         for (final JCSListener<K, V> listener : listeners.values())
477         {
478             listener.onRemoved(Collections.singletonList(new JCSCacheEntryEvent<>(this,
479                     EventType.REMOVED, null, key, value)));
480         }
481         if (remove && statisticsEnabled)
482         {
483             statistics.increaseRemovals(1);
484             statistics.addRemoveTime(Times.now(false) - start);
485         }
486         return remove;
487     }
488 
489     @Override
490     public boolean remove(final K key, final V oldValue)
491     {
492         assertNotClosed();
493         assertNotNull(key, "key");
494         assertNotNull(oldValue, "oldValue");
495         final long getStart = Times.now(false);
496         final V v = doGetControllingExpiry(getStart, key, false, false, false, false);
497         if (oldValue.equals(v))
498         {
499             remove(key);
500             return true;
501         }
502         if (v != null)
503         {
504             // weird but just for stats to be right (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
505             expiryPolicy.getExpiryForAccess();
506         }
507         return false;
508     }
509 
510     @Override
511     public V getAndRemove(final K key)
512     {
513         assertNotClosed();
514         assertNotNull(key, "key");
515         final long getStart = Times.now(false);
516         final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
517         remove(key);
518         return v;
519     }
520 
521     private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, final boolean skipLoad,
522             final boolean propagateLoadException)
523     {
524         final boolean statisticsEnabled = config.isStatisticsEnabled();
525         final ICacheElement<K, V> elt = delegate.get(key);
526         V v = elt != null ? elt.getVal() : null;
527         if (v == null && (config.isReadThrough() || forceDoLoad))
528         {
529             if (!skipLoad)
530             {
531                 v = doLoad(key, false, getStart, propagateLoadException);
532             }
533         }
534         else if (statisticsEnabled)
535         {
536             if (v != null)
537             {
538                 statistics.increaseHits(1);
539             }
540             else
541             {
542                 statistics.increaseMisses(1);
543             }
544         }
545 
546         if (updateAcess && elt != null)
547         {
548             final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
549             if (!isNotZero(expiryForAccess))
550             {
551                 forceExpires(key);
552             }
553             else if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
554             {
555                 try
556                 {
557                     delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
558                 }
559                 catch (final IOException e)
560                 {
561                     throw new CacheException(e);
562                 }
563             }
564         }
565         if (statisticsEnabled && v != null)
566         {
567             statistics.addGetTime(Times.now(false) - getStart);
568         }
569         return v;
570     }
571 
572     @Override
573     public boolean replace(final K key, final V oldValue, final V newValue)
574     {
575         assertNotClosed();
576         assertNotNull(key, "key");
577         assertNotNull(oldValue, "oldValue");
578         assertNotNull(newValue, "newValue");
579         final boolean statisticsEnabled = config.isStatisticsEnabled();
580         final ICacheElement<K, V> elt = delegate.get(key);
581         if (elt != null)
582         {
583             V value = elt.getVal();
584             if (value != null && statisticsEnabled)
585             {
586                 statistics.increaseHits(1);
587             }
588             if (value == null && config.isReadThrough())
589             {
590                 value = doLoad(key, false, Times.now(false), false);
591             }
592             if (value != null && value.equals(oldValue))
593             {
594                 put(key, newValue);
595                 return true;
596             }
597             if (value != null)
598             {
599                 final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
600                 if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
601                 {
602                     try
603                     {
604                         delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
605                     }
606                     catch (final IOException e)
607                     {
608                         throw new CacheException(e);
609                     }
610                 }
611             }
612         }
613         else if (statisticsEnabled)
614         {
615             statistics.increaseMisses(1);
616         }
617         return false;
618     }
619 
620     @Override
621     public boolean replace(final K key, final V value)
622     {
623         assertNotClosed();
624         assertNotNull(key, "key");
625         assertNotNull(value, "value");
626         final boolean statisticsEnabled = config.isStatisticsEnabled();
627         if (containsKey(key))
628         {
629             if (statisticsEnabled)
630             {
631                 statistics.increaseHits(1);
632             }
633             put(key, value);
634             return true;
635         }
636         if (statisticsEnabled)
637         {
638             statistics.increaseMisses(1);
639         }
640         return false;
641     }
642 
643     @Override
644     public V getAndReplace(final K key, final V value)
645     {
646         assertNotClosed();
647         assertNotNull(key, "key");
648         assertNotNull(value, "value");
649 
650         final boolean statisticsEnabled = config.isStatisticsEnabled();
651 
652         final ICacheElement<K, V> elt = delegate.get(key);
653         if (elt != null)
654         {
655             V oldValue = elt.getVal();
656             if (oldValue == null && config.isReadThrough())
657             {
658                 oldValue = doLoad(key, false, Times.now(false), false);
659             }
660             else if (statisticsEnabled)
661             {
662                 statistics.increaseHits(1);
663             }
664             put(key, value);
665             return oldValue;
666         }
667         if (statisticsEnabled)
668         {
669             statistics.increaseMisses(1);
670         }
671         return null;
672     }
673 
674     @Override
675     public void removeAll(final Set<? extends K> keys)
676     {
677         assertNotClosed();
678         assertNotNull(keys, "keys");
679         for (final K k : keys)
680         {
681             remove(k);
682         }
683     }
684 
685     @Override
686     public void removeAll()
687     {
688         assertNotClosed();
689         for (final K k : delegate.getKeySet())
690         {
691             remove(k);
692         }
693     }
694 
695     @Override
696     public void clear()
697     {
698         assertNotClosed();
699         try
700         {
701             delegate.removeAll();
702         }
703         catch (final IOException e)
704         {
705             throw new CacheException(e);
706         }
707     }
708 
709     @Override
710     public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz)
711     {
712         assertNotClosed();
713         return clazz.cast(config);
714     }
715 
716     @Override
717     public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
718     {
719         assertNotClosed();
720         assertNotNull(keys, "keys");
721         for (final K k : keys)
722         {
723             assertNotNull(k, "a key");
724         }
725         pool.submit(() -> doLoadAll(keys, replaceExistingValues, completionListener));
726     }
727 
728     private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
729     {
730         try
731         {
732             final long now = Times.now(false);
733             for (final K k : keys)
734             {
735                 if (replaceExistingValues)
736                 {
737                     doLoad(k, containsKey(k), now, completionListener != null);
738                     continue;
739                 }
740                 if (containsKey(k))
741                 {
742                     continue;
743                 }
744                 doGetControllingExpiry(now, k, true, true, false, completionListener != null);
745             }
746         }
747         catch (final RuntimeException e)
748         {
749             if (completionListener != null)
750             {
751                 completionListener.onException(e);
752                 return;
753             }
754         }
755         if (completionListener != null)
756         {
757             completionListener.onCompletion();
758         }
759     }
760 
761     @Override
762     public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
763     {
764         final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
765         final T t = doInvoke(view, key, entryProcessor, arguments);
766         view.merge();
767         return t;
768     }
769 
770     private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
771             final Object... arguments)
772     {
773         assertNotClosed();
774         assertNotNull(entryProcessor, "entryProcessor");
775         assertNotNull(key, "key");
776         try
777         {
778             if (config.isStatisticsEnabled())
779             {
780                 if (containsKey(key))
781                 {
782                     statistics.increaseHits(1);
783                 }
784                 else
785                 {
786                     statistics.increaseMisses(1);
787                 }
788             }
789             return entryProcessor.process(new JCSMutableEntry<>(view, key), arguments);
790         }
791         catch (final Exception ex)
792         {
793             return throwEntryProcessorException(ex);
794         }
795     }
796 
797     private static <T> T throwEntryProcessorException(final Exception ex)
798     {
799         if (EntryProcessorException.class.isInstance(ex))
800         {
801             throw EntryProcessorException.class.cast(ex);
802         }
803         throw new EntryProcessorException(ex);
804     }
805 
806     @Override
807     public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
808             final Object... arguments)
809     {
810         assertNotClosed();
811         assertNotNull(entryProcessor, "entryProcessor");
812         final Map<K, EntryProcessorResult<T>> results = new HashMap<>();
813         for (final K k : keys)
814         {
815             try
816             {
817                 final T invoke = invoke(k, entryProcessor, arguments);
818                 if (invoke != null)
819                 {
820                     results.put(k, () -> invoke);
821                 }
822             }
823             catch (final Exception e)
824             {
825                 results.put(k, () -> throwEntryProcessorException(e));
826             }
827         }
828         return results;
829     }
830 
831     @Override
832     public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
833     {
834         assertNotClosed();
835         if (listeners.containsKey(cacheEntryListenerConfiguration))
836         {
837             throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
838         }
839         listeners.put(cacheEntryListenerConfiguration, new JCSListener<>(cacheEntryListenerConfiguration));
840         config.addListener(cacheEntryListenerConfiguration);
841     }
842 
843     @Override
844     public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
845     {
846         assertNotClosed();
847         listeners.remove(cacheEntryListenerConfiguration);
848         config.removeListener(cacheEntryListenerConfiguration);
849     }
850 
851     @Override
852     public Iterator<Entry<K, V>> iterator()
853     {
854         assertNotClosed();
855         final Iterator<K> keys = new HashSet<>(delegate.getKeySet()).iterator();
856         return new Iterator<Entry<K, V>>()
857         {
858             private K lastKey;
859 
860             @Override
861             public boolean hasNext()
862             {
863                 return keys.hasNext();
864             }
865 
866             @Override
867             public Entry<K, V> next()
868             {
869                 lastKey = keys.next();
870                 return new JCSEntry<>(lastKey, get(lastKey));
871             }
872 
873             @Override
874             public void remove()
875             {
876                 if (isClosed() || lastKey == null)
877                 {
878                     throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
879                 }
880                 JCSCache.this.remove(lastKey);
881             }
882         };
883     }
884 
885     @Override
886     public String getName()
887     {
888         assertNotClosed();
889         return name;
890     }
891 
892     @Override
893     public CacheManager getCacheManager()
894     {
895         assertNotClosed();
896         return manager;
897     }
898 
899     @Override
900     public synchronized void close()
901     {
902         if (isClosed())
903         {
904             return;
905         }
906 
907         for (final Runnable task : pool.shutdownNow()) {
908             task.run();
909         }
910 
911         manager.release(getName());
912         closed = true;
913         close(loader);
914         close(writer);
915         close(expiryPolicy);
916         for (final JCSListener<K, V> listener : listeners.values())
917         {
918             close(listener);
919         }
920         listeners.clear();
921         JMXs.unregister(cacheConfigObjectName);
922         JMXs.unregister(cacheStatsObjectName);
923         try
924         {
925             delegate.removeAll();
926         }
927         catch (final IOException e)
928         {
929             throw new CacheException(e);
930         }
931     }
932 
933     private static void close(final Object potentiallyCloseable)
934     {
935         if (Closeable.class.isInstance(potentiallyCloseable))
936         {
937             Closeable.class.cast(potentiallyCloseable);
938         }
939     }
940 
941     @Override
942     public boolean isClosed()
943     {
944         return closed;
945     }
946 
947     @Override
948     public <T> T unwrap(final Class<T> clazz)
949     {
950         assertNotClosed();
951         if (clazz.isInstance(this))
952         {
953             return clazz.cast(this);
954         }
955         if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class))
956         {
957             return clazz.cast(delegate);
958         }
959         throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
960     }
961 
962     public Statistics getStatistics()
963     {
964         return statistics;
965     }
966 
967     public void enableManagement()
968     {
969         config.managementEnabled();
970         JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
971     }
972 
973     public void disableManagement()
974     {
975         config.managementDisabled();
976         JMXs.unregister(cacheConfigObjectName);
977     }
978 
979     public void enableStatistics()
980     {
981         config.statisticsEnabled();
982         statistics.setActive(true);
983         JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
984     }
985 
986     public void disableStatistics()
987     {
988         config.statisticsDisabled();
989         statistics.setActive(false);
990         JMXs.unregister(cacheStatsObjectName);
991     }
992 }