001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.jcs3.jcache;
020
021import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
022import static org.apache.commons.jcs3.jcache.serialization.Serializations.copy;
023
024import java.io.Closeable;
025import java.io.IOException;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.Map;
031import java.util.Properties;
032import java.util.Set;
033import java.util.concurrent.ConcurrentHashMap;
034import java.util.concurrent.ConcurrentMap;
035import java.util.concurrent.ExecutorService;
036import java.util.concurrent.Executors;
037
038import javax.cache.Cache;
039import javax.cache.CacheException;
040import javax.cache.CacheManager;
041import javax.cache.configuration.CacheEntryListenerConfiguration;
042import javax.cache.configuration.Configuration;
043import javax.cache.configuration.Factory;
044import javax.cache.event.EventType;
045import javax.cache.expiry.Duration;
046import javax.cache.expiry.EternalExpiryPolicy;
047import javax.cache.expiry.ExpiryPolicy;
048import javax.cache.integration.CacheLoader;
049import javax.cache.integration.CacheLoaderException;
050import javax.cache.integration.CacheWriter;
051import javax.cache.integration.CacheWriterException;
052import javax.cache.integration.CompletionListener;
053import javax.cache.processor.EntryProcessor;
054import javax.cache.processor.EntryProcessorException;
055import javax.cache.processor.EntryProcessorResult;
056import javax.management.ObjectName;
057
058import org.apache.commons.jcs3.engine.CacheElement;
059import org.apache.commons.jcs3.engine.ElementAttributes;
060import org.apache.commons.jcs3.engine.behavior.ICacheElement;
061import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
062import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
063import org.apache.commons.jcs3.jcache.jmx.JCSCacheMXBean;
064import org.apache.commons.jcs3.jcache.jmx.JCSCacheStatisticsMXBean;
065import org.apache.commons.jcs3.jcache.jmx.JMXs;
066import org.apache.commons.jcs3.jcache.proxy.ExceptionWrapperHandler;
067import org.apache.commons.jcs3.jcache.thread.DaemonThreadFactory;
068import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
069
070// TODO: configure serializer
071public class JCSCache<K, V> implements Cache<K, V>
072{
073    private final ExpiryAwareCache<K, V> delegate;
074    private final JCSCachingManager manager;
075    private final JCSConfiguration<K, V> config;
076    private final CacheLoader<K, V> loader;
077    private final CacheWriter<? super K, ? super V> writer;
078    private final ExpiryPolicy expiryPolicy;
079    private final ObjectName cacheConfigObjectName;
080    private final ObjectName cacheStatsObjectName;
081    private final String name;
082    private volatile boolean closed;
083    private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<>();
084    private final Statistics statistics = new Statistics();
085    private final ExecutorService pool;
086    private final IElementSerializer serializer; // using json/xml should work as well -> don't force Serializable
087
088
089    public JCSCache(final ClassLoader classLoader, final JCSCachingManager mgr,
090                    final String cacheName, final JCSConfiguration<K, V> configuration,
091                    final Properties properties, final ExpiryAwareCache<K, V> cache)
092    {
093        manager = mgr;
094
095        name = cacheName;
096
097        delegate = cache;
098        if (delegate.getElementAttributes() == null)
099        {
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}