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.jcs.jcache;
020
021import org.apache.commons.jcs.engine.CacheElement;
022import org.apache.commons.jcs.engine.ElementAttributes;
023import org.apache.commons.jcs.engine.behavior.ICacheElement;
024import org.apache.commons.jcs.engine.behavior.IElementAttributes;
025import org.apache.commons.jcs.engine.behavior.IElementSerializer;
026import org.apache.commons.jcs.jcache.jmx.JCSCacheMXBean;
027import org.apache.commons.jcs.jcache.jmx.JCSCacheStatisticsMXBean;
028import org.apache.commons.jcs.jcache.jmx.JMXs;
029import org.apache.commons.jcs.jcache.proxy.ExceptionWrapperHandler;
030import org.apache.commons.jcs.jcache.thread.DaemonThreadFactory;
031import org.apache.commons.jcs.utils.serialization.StandardSerializer;
032
033import javax.cache.Cache;
034import javax.cache.CacheException;
035import javax.cache.CacheManager;
036import javax.cache.configuration.CacheEntryListenerConfiguration;
037import javax.cache.configuration.Configuration;
038import javax.cache.configuration.Factory;
039import javax.cache.event.CacheEntryEvent;
040import javax.cache.event.EventType;
041import javax.cache.expiry.Duration;
042import javax.cache.expiry.EternalExpiryPolicy;
043import javax.cache.expiry.ExpiryPolicy;
044import javax.cache.integration.CacheLoader;
045import javax.cache.integration.CacheLoaderException;
046import javax.cache.integration.CacheWriter;
047import javax.cache.integration.CacheWriterException;
048import javax.cache.integration.CompletionListener;
049import javax.cache.processor.EntryProcessor;
050import javax.cache.processor.EntryProcessorException;
051import javax.cache.processor.EntryProcessorResult;
052import javax.management.ObjectName;
053import java.io.Closeable;
054import java.io.IOException;
055import java.util.Arrays;
056import java.util.HashMap;
057import java.util.HashSet;
058import java.util.Iterator;
059import java.util.Map;
060import java.util.Properties;
061import java.util.Set;
062import java.util.concurrent.ConcurrentHashMap;
063import java.util.concurrent.ConcurrentMap;
064import java.util.concurrent.ExecutorService;
065import java.util.concurrent.Executors;
066
067import static org.apache.commons.jcs.jcache.Asserts.assertNotNull;
068import static org.apache.commons.jcs.jcache.serialization.Serializations.copy;
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 = false;
083    private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>>();
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.class.cast(classLoader.loadClass(property(properties, "serializer", cacheName, StandardSerializer.class.getName())).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 = 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 = 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<K, V>(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<K, V>(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<K, V>(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<K, V>(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<K, V>();
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<K, V>(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(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
385                            EventType.CREATED, null, key, value)));
386                }
387                else
388                {
389                    listener.onUpdated(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(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(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(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<K, V>(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        final K cacheKey = key;
471
472        final ICacheElement<K, V> v = delegate.get(cacheKey);
473        delegate.remove(cacheKey);
474
475        final V value = v != null && v.getVal() != null ? v.getVal() : null;
476        boolean remove = v != null;
477        for (final JCSListener<K, V> listener : listeners.values())
478        {
479            listener.onRemoved(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
480                    EventType.REMOVED, null, key, value)));
481        }
482        if (remove && statisticsEnabled)
483        {
484            statistics.increaseRemovals(1);
485            statistics.addRemoveTime(Times.now(false) - start);
486        }
487        return remove;
488    }
489
490    @Override
491    public boolean remove(final K key, final V oldValue)
492    {
493        assertNotClosed();
494        assertNotNull(key, "key");
495        assertNotNull(oldValue, "oldValue");
496        final long getStart = Times.now(false);
497        final V v = doGetControllingExpiry(getStart, key, false, false, false, false);
498        if (oldValue.equals(v))
499        {
500            remove(key);
501            return true;
502        }
503        else if (v != null)
504        {
505            // weird but just for stats to be right (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
506            expiryPolicy.getExpiryForAccess();
507        }
508        return false;
509    }
510
511    @Override
512    public V getAndRemove(final K key)
513    {
514        assertNotClosed();
515        assertNotNull(key, "key");
516        final long getStart = Times.now(false);
517        final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
518        remove(key);
519        return v;
520    }
521
522    private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, final boolean skipLoad,
523            final boolean propagateLoadException)
524    {
525        final boolean statisticsEnabled = config.isStatisticsEnabled();
526        final ICacheElement<K, V> elt = delegate.get(key);
527        V v = elt != null ? elt.getVal() : null;
528        if (v == null && (config.isReadThrough() || forceDoLoad))
529        {
530            if (!skipLoad)
531            {
532                v = doLoad(key, false, getStart, propagateLoadException);
533            }
534        }
535        else if (statisticsEnabled)
536        {
537            if (v != null)
538            {
539                statistics.increaseHits(1);
540            }
541            else
542            {
543                statistics.increaseMisses(1);
544            }
545        }
546
547        if (updateAcess && elt != null)
548        {
549            final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
550            if (!isNotZero(expiryForAccess))
551            {
552                forceExpires(key);
553            }
554            else if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
555            {
556                try
557                {
558                    delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
559                }
560                catch (final IOException e)
561                {
562                    throw new CacheException(e);
563                }
564            }
565        }
566        if (statisticsEnabled && v != null)
567        {
568            statistics.addGetTime(Times.now(false) - getStart);
569        }
570        return v;
571    }
572
573    @Override
574    public boolean replace(final K key, final V oldValue, final V newValue)
575    {
576        assertNotClosed();
577        assertNotNull(key, "key");
578        assertNotNull(oldValue, "oldValue");
579        assertNotNull(newValue, "newValue");
580        final boolean statisticsEnabled = config.isStatisticsEnabled();
581        final ICacheElement<K, V> elt = delegate.get(key);
582        if (elt != null)
583        {
584            V value = elt.getVal();
585            if (value != null && statisticsEnabled)
586            {
587                statistics.increaseHits(1);
588            }
589            if (value == null && config.isReadThrough())
590            {
591                value = doLoad(key, false, Times.now(false), false);
592            }
593            if (value != null && value.equals(oldValue))
594            {
595                put(key, newValue);
596                return true;
597            }
598            else if (value != null)
599            {
600                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
601                if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
602                {
603                    try
604                    {
605                        delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
606                    }
607                    catch (final IOException e)
608                    {
609                        throw new CacheException(e);
610                    }
611                }
612            }
613        }
614        else if (statisticsEnabled)
615        {
616            statistics.increaseMisses(1);
617        }
618        return false;
619    }
620
621    @Override
622    public boolean replace(final K key, final V value)
623    {
624        assertNotClosed();
625        assertNotNull(key, "key");
626        assertNotNull(value, "value");
627        boolean statisticsEnabled = config.isStatisticsEnabled();
628        if (containsKey(key))
629        {
630            if (statisticsEnabled)
631            {
632                statistics.increaseHits(1);
633            }
634            put(key, value);
635            return true;
636        }
637        else if (statisticsEnabled)
638        {
639            statistics.increaseMisses(1);
640        }
641        return false;
642    }
643
644    @Override
645    public V getAndReplace(final K key, final V value)
646    {
647        assertNotClosed();
648        assertNotNull(key, "key");
649        assertNotNull(value, "value");
650
651        final boolean statisticsEnabled = config.isStatisticsEnabled();
652
653        final ICacheElement<K, V> elt = delegate.get(key);
654        if (elt != null)
655        {
656            V oldValue = elt.getVal();
657            if (oldValue == null && config.isReadThrough())
658            {
659                oldValue = doLoad(key, false, Times.now(false), false);
660            }
661            else if (statisticsEnabled)
662            {
663                statistics.increaseHits(1);
664            }
665            put(key, value);
666            return oldValue;
667        }
668        else if (statisticsEnabled)
669        {
670            statistics.increaseMisses(1);
671        }
672        return null;
673    }
674
675    @Override
676    public void removeAll(final Set<? extends K> keys)
677    {
678        assertNotClosed();
679        assertNotNull(keys, "keys");
680        for (final K k : keys)
681        {
682            remove(k);
683        }
684    }
685
686    @Override
687    public void removeAll()
688    {
689        assertNotClosed();
690        for (final K k : delegate.getKeySet())
691        {
692            remove(k);
693        }
694    }
695
696    @Override
697    public void clear()
698    {
699        assertNotClosed();
700        try
701        {
702            delegate.removeAll();
703        }
704        catch (final IOException e)
705        {
706            throw new CacheException(e);
707        }
708    }
709
710    @Override
711    public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz)
712    {
713        assertNotClosed();
714        return clazz.cast(config);
715    }
716
717    @Override
718    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
719    {
720        assertNotClosed();
721        assertNotNull(keys, "keys");
722        for (final K k : keys)
723        {
724            assertNotNull(k, "a key");
725        }
726        pool.submit(new Runnable()
727        {
728            @Override
729            public void run()
730            {
731                doLoadAll(keys, replaceExistingValues, completionListener);
732            }
733        });
734    }
735
736    private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
737    {
738        try
739        {
740            final long now = Times.now(false);
741            for (final K k : keys)
742            {
743                if (replaceExistingValues)
744                {
745                    doLoad(k, containsKey(k), now, completionListener != null);
746                    continue;
747                }
748                else if (containsKey(k))
749                {
750                    continue;
751                }
752                doGetControllingExpiry(now, k, true, true, false, completionListener != null);
753            }
754        }
755        catch (final RuntimeException e)
756        {
757            if (completionListener != null)
758            {
759                completionListener.onException(e);
760                return;
761            }
762        }
763        if (completionListener != null)
764        {
765            completionListener.onCompletion();
766        }
767    }
768
769    @Override
770    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
771    {
772        final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
773        final T t = doInvoke(view, key, entryProcessor, arguments);
774        view.merge();
775        return t;
776    }
777
778    private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
779            final Object... arguments)
780    {
781        assertNotClosed();
782        assertNotNull(entryProcessor, "entryProcessor");
783        assertNotNull(key, "key");
784        try
785        {
786            if (config.isStatisticsEnabled())
787            {
788                if (containsKey(key))
789                {
790                    statistics.increaseHits(1);
791                }
792                else
793                {
794                    statistics.increaseMisses(1);
795                }
796            }
797            return entryProcessor.process(new JCSMutableEntry<K, V>(view, key), arguments);
798        }
799        catch (final Exception ex)
800        {
801            return throwEntryProcessorException(ex);
802        }
803    }
804
805    private static <T> T throwEntryProcessorException(final Exception ex)
806    {
807        if (EntryProcessorException.class.isInstance(ex))
808        {
809            throw EntryProcessorException.class.cast(ex);
810        }
811        throw new EntryProcessorException(ex);
812    }
813
814    @Override
815    public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
816            final Object... arguments)
817    {
818        assertNotClosed();
819        assertNotNull(entryProcessor, "entryProcessor");
820        final Map<K, EntryProcessorResult<T>> results = new HashMap<K, EntryProcessorResult<T>>();
821        for (final K k : keys)
822        {
823            try
824            {
825                final T invoke = invoke(k, entryProcessor, arguments);
826                if (invoke != null)
827                {
828                    results.put(k, new EntryProcessorResult<T>()
829                    {
830                        @Override
831                        public T get() throws EntryProcessorException
832                        {
833                            return invoke;
834                        }
835                    });
836                }
837            }
838            catch (final Exception e)
839            {
840                results.put(k, new EntryProcessorResult<T>()
841                {
842                    @Override
843                    public T get() throws EntryProcessorException
844                    {
845                        return throwEntryProcessorException(e);
846                    }
847                });
848            }
849        }
850        return results;
851    }
852
853    @Override
854    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
855    {
856        assertNotClosed();
857        if (listeners.containsKey(cacheEntryListenerConfiguration))
858        {
859            throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
860        }
861        listeners.put(cacheEntryListenerConfiguration, new JCSListener<K, V>(cacheEntryListenerConfiguration));
862        config.addListener(cacheEntryListenerConfiguration);
863    }
864
865    @Override
866    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
867    {
868        assertNotClosed();
869        listeners.remove(cacheEntryListenerConfiguration);
870        config.removeListener(cacheEntryListenerConfiguration);
871    }
872
873    @Override
874    public Iterator<Entry<K, V>> iterator()
875    {
876        assertNotClosed();
877        final Iterator<K> keys = new HashSet<K>(delegate.getKeySet()).iterator();
878        return new Iterator<Entry<K, V>>()
879        {
880            private K lastKey = null;
881
882            @Override
883            public boolean hasNext()
884            {
885                return keys.hasNext();
886            }
887
888            @Override
889            public Entry<K, V> next()
890            {
891                lastKey = keys.next();
892                return new JCSEntry<K, V>(lastKey, get(lastKey));
893            }
894
895            @Override
896            public void remove()
897            {
898                if (isClosed() || lastKey == null)
899                {
900                    throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
901                }
902                JCSCache.this.remove(lastKey);
903            }
904        };
905    }
906
907    @Override
908    public String getName()
909    {
910        assertNotClosed();
911        return name;
912    }
913
914    @Override
915    public CacheManager getCacheManager()
916    {
917        assertNotClosed();
918        return manager;
919    }
920
921    @Override
922    public synchronized void close()
923    {
924        if (isClosed())
925        {
926            return;
927        }
928
929        for (final Runnable task : pool.shutdownNow()) {
930            task.run();
931        }
932
933        manager.release(getName());
934        closed = true;
935        close(loader);
936        close(writer);
937        close(expiryPolicy);
938        for (final JCSListener<K, V> listener : listeners.values())
939        {
940            close(listener);
941        }
942        listeners.clear();
943        JMXs.unregister(cacheConfigObjectName);
944        JMXs.unregister(cacheStatsObjectName);
945        try
946        {
947            delegate.removeAll();
948        }
949        catch (final IOException e)
950        {
951            throw new CacheException(e);
952        }
953    }
954
955    private static void close(final Object potentiallyCloseable)
956    {
957        if (Closeable.class.isInstance(potentiallyCloseable))
958        {
959            Closeable.class.cast(potentiallyCloseable);
960        }
961    }
962
963    @Override
964    public boolean isClosed()
965    {
966        return closed;
967    }
968
969    @Override
970    public <T> T unwrap(final Class<T> clazz)
971    {
972        assertNotClosed();
973        if (clazz.isInstance(this))
974        {
975            return clazz.cast(this);
976        }
977        if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class))
978        {
979            return clazz.cast(delegate);
980        }
981        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
982    }
983
984    public Statistics getStatistics()
985    {
986        return statistics;
987    }
988
989    public void enableManagement()
990    {
991        config.managementEnabled();
992        JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<K, V>(this));
993    }
994
995    public void disableManagement()
996    {
997        config.managementDisabled();
998        JMXs.unregister(cacheConfigObjectName);
999    }
1000
1001    public void enableStatistics()
1002    {
1003        config.statisticsEnabled();
1004        statistics.setActive(true);
1005        JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
1006    }
1007
1008    public void disableStatistics()
1009    {
1010        config.statisticsDisabled();
1011        statistics.setActive(false);
1012        JMXs.unregister(cacheStatsObjectName);
1013    }
1014}