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}