1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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;
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
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(
347 jcsKey, value, created ? null : duration,
348 oldElt != null ? oldElt.getElementAttributes() : delegate.getElementAttributes().clone());
349 if (created && duration != null) {
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
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 }