1 5 package com.opensymphony.oscache.base; 6 7 import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache; 8 import com.opensymphony.oscache.base.algorithm.LRUCache; 9 import com.opensymphony.oscache.base.algorithm.UnlimitedCache; 10 import com.opensymphony.oscache.base.events.*; 11 import com.opensymphony.oscache.base.persistence.PersistenceListener; 12 import com.opensymphony.oscache.util.FastCronParser; 13 14 import org.apache.commons.logging.Log; 15 import org.apache.commons.logging.LogFactory; 16 17 import java.io.Serializable ; 18 19 import java.text.ParseException ; 20 21 import java.util.*; 22 23 import javax.swing.event.EventListenerList ; 24 25 37 public class Cache implements Serializable { 38 41 public static final String NESTED_EVENT = "NESTED"; 42 private static transient final Log log = LogFactory.getLog(Cache.class); 43 44 47 protected EventListenerList listenerList = new EventListenerList (); 48 49 52 private AbstractConcurrentReadCache cacheMap = null; 53 54 57 private Date flushDateTime = null; 58 59 77 private Map updateStates = new HashMap(); 78 79 83 private boolean blocking = false; 84 85 92 public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) { 93 this(useMemoryCaching, unlimitedDiskCache, overflowPersistence, false, null, 0); 94 } 95 96 119 public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String algorithmClass, int capacity) { 120 if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) { 122 try { 123 cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance(); 124 cacheMap.setMaxEntries(capacity); 125 } catch (Exception e) { 126 log.error("Invalid class name for cache algorithm class. " + e.toString()); 127 } 128 } 129 130 if (cacheMap == null) { 131 if (capacity > 0) { 133 cacheMap = new LRUCache(capacity); 134 } else { 135 cacheMap = new UnlimitedCache(); 136 } 137 } 138 139 cacheMap.setUnlimitedDiskCache(unlimitedDiskCache); 140 cacheMap.setOverflowPersistence(overflowPersistence); 141 cacheMap.setMemoryCaching(useMemoryCaching); 142 143 this.blocking = blocking; 144 } 145 146 149 public int getCapacity() { 150 return cacheMap.getMaxEntries(); 151 } 152 153 160 public void setCapacity(int capacity) { 161 cacheMap.setMaxEntries(capacity); 162 } 163 164 171 public boolean isFlushed(CacheEntry cacheEntry) { 172 if (flushDateTime != null) { 173 final long lastUpdate = cacheEntry.getLastUpdate(); 174 final long flushTime = flushDateTime.getTime(); 175 176 return (flushTime <= System.currentTimeMillis()) && (flushTime >= lastUpdate); 178 } else { 179 return false; 180 } 181 } 182 183 198 public Object getFromCache(String key) throws NeedsRefreshException { 199 return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null); 200 } 201 202 220 public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException { 221 return getFromCache(key, refreshPeriod, null); 222 } 223 224 245 public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException { 246 CacheEntry cacheEntry = this.getCacheEntry(key, null, null); 247 248 Object content = cacheEntry.getContent(); 249 CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT; 250 251 boolean reload = false; 252 253 if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) { 257 258 EntryUpdateState updateState = getUpdateState(key); 260 try { 261 synchronized (updateState) { 262 if (updateState.isAwaitingUpdate() || updateState.isCancelled()) { 263 updateState.startUpdate(); 265 266 if (cacheEntry.isNew()) { 267 accessEventType = CacheMapAccessEventType.MISS; 268 } else { 269 accessEventType = CacheMapAccessEventType.STALE_HIT; 270 } 271 } else if (updateState.isUpdating()) { 272 if (cacheEntry.isNew() || blocking) { 276 do { 277 try { 278 updateState.wait(); 279 } catch (InterruptedException e) { 280 } 281 } while (updateState.isUpdating()); 282 283 if (updateState.isCancelled()) { 284 updateState.startUpdate(); 287 288 if (cacheEntry.isNew()) { 289 accessEventType = CacheMapAccessEventType.MISS; 290 } else { 291 accessEventType = CacheMapAccessEventType.STALE_HIT; 292 } 293 } else if (updateState.isComplete()) { 294 reload = true; 295 } else { 296 log.error("Invalid update state for cache entry " + key); 297 } 298 } 299 } else { 300 reload = true; 301 } 302 } 303 } finally { 304 releaseUpdateState(updateState, key); 307 } 308 } 309 310 if (reload) { 312 cacheEntry = (CacheEntry) cacheMap.get(key); 313 314 if (cacheEntry != null) { 315 content = cacheEntry.getContent(); 316 } else { 317 log.error("Could not reload cache entry after waiting for it to be rebuilt"); 318 } 319 } 320 321 dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null); 322 323 if (accessEventType != CacheMapAccessEventType.HIT) { 325 throw new NeedsRefreshException(content); 326 } 327 328 return content; 329 } 330 331 337 public void setPersistenceListener(PersistenceListener listener) { 338 cacheMap.setPersistenceListener(listener); 339 } 340 341 347 public PersistenceListener getPersistenceListener() { 348 return cacheMap.getPersistenceListener(); 349 } 350 351 357 public void addCacheEventListener(CacheEventListener listener, Class clazz) { 358 if (CacheEventListener.class.isAssignableFrom(clazz)) { 359 listenerList.add(clazz, listener); 360 } else { 361 log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener."); 362 } 363 } 364 365 369 public EventListenerList getCacheEventListenerList() { 370 return listenerList; 371 } 372 373 386 public void cancelUpdate(String key) { 387 EntryUpdateState state; 388 389 if (key != null) { 390 synchronized (updateStates) { 391 state = (EntryUpdateState) updateStates.get(key); 392 393 if (state != null) { 394 synchronized (state) { 395 int usageCounter = state.cancelUpdate(); 396 state.notify(); 397 398 checkEntryStateUpdateUsage(key, state, usageCounter); 399 } 400 } else { 401 if (log.isErrorEnabled()) { 402 log.error("internal error: expected to get a state from key [" + key + "]"); 403 } 404 } 405 } 406 } 407 } 408 409 415 private void checkEntryStateUpdateUsage(String key, EntryUpdateState state, int usageCounter) { 416 if (usageCounter ==0) { 418 EntryUpdateState removedState = (EntryUpdateState) updateStates.remove(key); 419 if (state != removedState) { 420 if (log.isErrorEnabled()) { 421 log.error("internal error: removed state [" + removedState + "] from key [" + key + "] whereas we expected [" + state + "]"); 422 try { 423 throw new Exception ("states not equal"); 424 } catch (Exception e) { 425 e.printStackTrace(); 427 } 428 } 429 } 430 } 431 } 432 433 438 public void flushAll(Date date) { 439 flushAll(date, null); 440 } 441 442 448 public void flushAll(Date date, String origin) { 449 flushDateTime = date; 450 451 if (listenerList.getListenerCount() > 0) { 452 dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin); 453 } 454 } 455 456 464 public void flushEntry(String key) { 465 flushEntry(key, null); 466 } 467 468 477 public void flushEntry(String key, String origin) { 478 flushEntry(getCacheEntry(key, null, origin), origin); 479 } 480 481 487 public void flushGroup(String group) { 488 flushGroup(group, null); 489 } 490 491 499 public void flushGroup(String group, String origin) { 500 Set groupEntries = cacheMap.getGroup(group); 502 503 if (groupEntries != null) { 504 Iterator itr = groupEntries.iterator(); 505 String key; 506 CacheEntry entry; 507 508 while (itr.hasNext()) { 509 key = (String ) itr.next(); 510 entry = (CacheEntry) cacheMap.get(key); 511 512 if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) { 513 flushEntry(entry, NESTED_EVENT); 514 } 515 } 516 } 517 518 if (listenerList.getListenerCount() > 0) { 519 dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin); 520 } 521 } 522 523 531 public void flushPattern(String pattern) { 532 flushPattern(pattern, null); 533 } 534 535 544 public void flushPattern(String pattern, String origin) { 545 if ((pattern != null) && (pattern.length() > 0)) { 547 String key = null; 548 CacheEntry entry = null; 549 Iterator itr = cacheMap.keySet().iterator(); 550 551 while (itr.hasNext()) { 552 key = (String ) itr.next(); 553 554 if (key.indexOf(pattern) >= 0) { 555 entry = (CacheEntry) cacheMap.get(key); 556 557 if (entry != null) { 558 flushEntry(entry, origin); 559 } 560 } 561 } 562 563 if (listenerList.getListenerCount() > 0) { 564 dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin); 565 } 566 } else { 567 } 569 } 570 571 577 public void putInCache(String key, Object content) { 578 putInCache(key, content, null, null, null); 579 } 580 581 588 public void putInCache(String key, Object content, EntryRefreshPolicy policy) { 589 putInCache(key, content, null, policy, null); 590 } 591 592 600 public void putInCache(String key, Object content, String [] groups) { 601 putInCache(key, content, groups, null, null); 602 } 603 604 613 public void putInCache(String key, Object content, String [] groups, EntryRefreshPolicy policy, String origin) { 614 CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin); 615 boolean isNewEntry = cacheEntry.isNew(); 616 617 if (!isNewEntry) { 619 cacheEntry = new CacheEntry(key, policy); 620 } 621 622 cacheEntry.setContent(content); 623 cacheEntry.setGroups(groups); 624 cacheMap.put(key, cacheEntry); 625 626 completeUpdate(key); 629 630 if (listenerList.getListenerCount() > 0) { 631 CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin); 632 633 if (isNewEntry) { 634 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event); 635 } else { 636 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event); 637 } 638 } 639 } 640 641 646 public void removeCacheEventListener(CacheEventListener listener, Class clazz) { 647 listenerList.remove(clazz, listener); 648 } 649 650 658 protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) { 659 CacheEntry cacheEntry = null; 660 661 if ((key == null) || (key.length() == 0)) { 663 throw new IllegalArgumentException ("getCacheEntry called with an empty or null key"); 664 } 665 666 cacheEntry = (CacheEntry) cacheMap.get(key); 667 668 if (cacheEntry == null) { 670 if (log.isDebugEnabled()) { 671 log.debug("No cache entry exists for key='" + key + "', creating"); 672 } 673 674 cacheEntry = new CacheEntry(key, policy); 675 } 676 677 return cacheEntry; 678 } 679 680 691 protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) { 692 boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry); 693 694 if ((!result) && (cronExpiry != null) && (cronExpiry.length() > 0)) { 695 try { 696 FastCronParser parser = new FastCronParser(cronExpiry); 697 result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate()); 698 } catch (ParseException e) { 699 log.warn(e); 700 } 701 } 702 703 return result; 704 } 705 706 716 protected EntryUpdateState getUpdateState(String key) { 717 EntryUpdateState updateState; 718 719 synchronized (updateStates) { 720 updateState = (EntryUpdateState) updateStates.get(key); 722 723 if (updateState == null) { 724 updateState = new EntryUpdateState(); 726 updateStates.put(key, updateState); 727 } else { 728 updateState.incrementUsageCounter(); 730 } 731 } 732 733 return updateState; 734 } 735 736 741 protected void releaseUpdateState(EntryUpdateState state, String key) { 742 synchronized (updateStates) { 743 int usageCounter = state.decrementUsageCounter(); 744 checkEntryStateUpdateUsage(key, state, usageCounter); 745 } 746 } 747 748 751 protected void clear() { 752 cacheMap.clear(); 753 } 754 755 763 protected void completeUpdate(String key) { 764 EntryUpdateState state; 765 766 synchronized (updateStates) { 767 state = (EntryUpdateState) updateStates.get(key); 768 769 if (state != null) { 770 synchronized (state) { 771 int usageCounter = state.completeUpdate(); 772 state.notifyAll(); 773 774 checkEntryStateUpdateUsage(key, state, usageCounter); 775 776 } 777 } else { 778 } 780 } 781 } 782 783 789 public void removeEntry(String key) { 790 removeEntry(key, null); 791 } 792 793 800 protected void removeEntry(String key, String origin) { 801 CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key); 802 cacheMap.remove(key); 803 804 if (listenerList.getListenerCount() > 0) { 805 CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin); 806 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED, event); 807 } 808 } 809 810 816 private void dispatchCacheEntryEvent(CacheEntryEventType eventType, CacheEntryEvent event) { 817 Object [] listeners = listenerList.getListenerList(); 819 820 for (int i = listeners.length - 2; i >= 0; i -= 2) { 823 if (listeners[i] == CacheEntryEventListener.class) { 824 if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) { 825 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryAdded(event); 826 } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) { 827 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryUpdated(event); 828 } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) { 829 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryFlushed(event); 830 } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) { 831 ((CacheEntryEventListener) listeners[i + 1]).cacheEntryRemoved(event); 832 } 833 } 834 } 835 } 836 837 844 private void dispatchCacheGroupEvent(CacheEntryEventType eventType, String group, String origin) { 845 CacheGroupEvent event = new CacheGroupEvent(this, group, origin); 846 847 Object [] listeners = listenerList.getListenerList(); 849 850 for (int i = listeners.length - 2; i >= 0; i -= 2) { 853 if (listeners[i] == CacheEntryEventListener.class) { 854 if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) { 855 ((CacheEntryEventListener) listeners[i + 1]).cacheGroupFlushed(event); 856 } 857 } 858 } 859 } 860 861 868 private void dispatchCacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) { 869 CacheMapAccessEvent event = new CacheMapAccessEvent(eventType, entry, origin); 870 871 Object [] listeners = listenerList.getListenerList(); 873 874 for (int i = listeners.length - 2; i >= 0; i -= 2) { 877 if (listeners[i] == CacheMapAccessEventListener.class) { 878 ((CacheMapAccessEventListener) listeners[i + 1]).accessed(event); 879 } 880 } 881 } 882 883 890 private void dispatchCachePatternEvent(CacheEntryEventType eventType, String pattern, String origin) { 891 CachePatternEvent event = new CachePatternEvent(this, pattern, origin); 892 893 Object [] listeners = listenerList.getListenerList(); 895 896 for (int i = listeners.length - 2; i >= 0; i -= 2) { 899 if (listeners[i] == CacheEntryEventListener.class) { 900 if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) { 901 ((CacheEntryEventListener) listeners[i + 1]).cachePatternFlushed(event); 902 } 903 } 904 } 905 } 906 907 913 private void dispatchCachewideEvent(CachewideEventType eventType, Date date, String origin) { 914 CachewideEvent event = new CachewideEvent(this, date, origin); 915 916 Object [] listeners = listenerList.getListenerList(); 918 919 for (int i = listeners.length - 2; i >= 0; i -= 2) { 922 if (listeners[i] == CacheEntryEventListener.class) { 923 if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) { 924 ((CacheEntryEventListener) listeners[i + 1]).cacheFlushed(event); 925 } 926 } 927 } 928 } 929 930 937 private void flushEntry(CacheEntry entry, String origin) { 938 String key = entry.getKey(); 939 940 entry.flush(); 942 943 if (!entry.isNew()) { 944 cacheMap.put(key, entry); 946 } 947 948 if (listenerList.getListenerCount() > 0) { 950 CacheEntryEvent event = new CacheEntryEvent(this, entry, origin); 951 dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED, event); 952 } 953 } 954 955 958 public int getSize() { 959 synchronized(cacheMap) { 960 return cacheMap.size(); 961 } 962 } 963 964 967 protected int getNbUpdateState() { 968 synchronized(updateStates) { 969 return updateStates.size(); 970 } 971 } 972 973 974 978 public int getNbEntries() { 979 synchronized(cacheMap) { 980 return cacheMap.size(); 981 } 982 } 983 } 984 | Popular Tags |