0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/memory/tags/sakai_2-4-1/memory-impl/impl/src/java/org/sakaiproject/memory/impl/MemCache.java $
0003: * $Id: MemCache.java 12712 2006-07-24 04:09:14Z ggolden@umich.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.memory.impl;
0021:
0022: import java.lang.ref.SoftReference;
0023: import java.util.HashSet;
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.Map;
0027: import java.util.Observable;
0028: import java.util.Observer;
0029: import java.util.Set;
0030: import java.util.Vector;
0031:
0032: import org.apache.commons.logging.Log;
0033: import org.apache.commons.logging.LogFactory;
0034: import org.sakaiproject.component.cover.ComponentManager;
0035: import org.sakaiproject.event.api.Event;
0036: import org.sakaiproject.event.api.EventTrackingService;
0037: import org.sakaiproject.memory.api.Cache;
0038: import org.sakaiproject.memory.api.CacheRefresher;
0039: import org.sakaiproject.memory.api.DerivedCache;
0040:
0041: import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
0042:
0043: /**
0044: * <p>
0045: * A Cache of objects with keys with a limited lifespan.
0046: * </p>
0047: * <p>
0048: * When the object expires, the cache calls upon a CacheRefresher to update the key's value. The update is done in a separate thread.
0049: * </p>
0050: */
0051: public class MemCache implements Cache, Runnable, Observer {
0052: /** Our logger. */
0053: private static Log M_log = LogFactory.getLog(MemCache.class);
0054:
0055: /** Map holding cached entries. */
0056: protected Map m_map = null;
0057:
0058: /** The object that will deal with expired entries. */
0059: protected CacheRefresher m_refresher = null;
0060:
0061: /** The string that all resources in this cache will start with. */
0062: protected String m_resourcePattern = null;
0063:
0064: /** The number of seconds to sleep between expiration checks. */
0065: protected long m_refresherSleep = 60;
0066:
0067: /** If true, we are disabled. */
0068: protected boolean m_disabled = false;
0069:
0070: /** If true, we have all the entries that there are in the cache. */
0071: protected boolean m_complete = false;
0072:
0073: /** Alternate isComplete, based on patterns. */
0074: protected Set m_partiallyComplete = new HashSet();
0075:
0076: /** If true, we are going to hold any events we see in the m_heldEvents list for later processing. */
0077: protected boolean m_holdEventProcessing = false;
0078:
0079: /** The events we are holding for later processing. */
0080: protected List m_heldEvents = new Vector();
0081:
0082: /** Constructor injected memory service. */
0083: protected BasicMemoryService m_memoryService = null;
0084:
0085: /** Constructor injected event tracking service. */
0086: protected EventTrackingService m_eventTrackingService = null;
0087:
0088: /** If true, we do soft references, else we do hard ones. */
0089: protected boolean m_softRefs = true;
0090:
0091: /** Count of access requests. */
0092: protected long m_getCount = 0;
0093:
0094: /** Count of access requests satisfied with a cached entry. */
0095: protected long m_hitCount = 0;
0096:
0097: /** Count of things put into the cache. */
0098: protected long m_putCount = 0;
0099:
0100: /** My (optional) DerivedCache. */
0101: protected DerivedCache m_derivedCache = null;
0102:
0103: /**
0104: * The cache entry. Holds a time stamped payload.
0105: */
0106: protected class CacheEntry extends SoftReference {
0107: /** currentTimeMillis when this expires. */
0108: protected long m_expires = 0;
0109:
0110: /** The time (seconds) to keep this cached (0 means don't exipre). */
0111: protected int m_duration = 0;
0112:
0113: /** Set if our payload is supposed to be null. */
0114: protected boolean m_nullPayload = false;
0115:
0116: /** Hard reference to the payload, if needed. */
0117: protected Object m_hardPayload = null;
0118:
0119: /**
0120: * Construct to cache the payload for the duration.
0121: *
0122: * @param payload
0123: * The thing to cache.
0124: * @param duration
0125: * The time (seconds) to keep this cached.
0126: */
0127: public CacheEntry(Object payload, int duration) {
0128: // put the payload into the soft reference
0129: super (payload);
0130:
0131: // if we are not doing soft refs, make the hard ref and clear the soft
0132: if (!m_softRefs) {
0133: this .clear();
0134: m_hardPayload = payload;
0135: }
0136:
0137: // is it supposed to be null?
0138: m_nullPayload = (payload == null);
0139:
0140: m_duration = duration;
0141: reset();
0142:
0143: } // CacheEntry
0144:
0145: /**
0146: * Access the hard payload directly.
0147: * @return The hard payload.
0148: */
0149: public Object getHardPayload() {
0150: return m_hardPayload;
0151: }
0152:
0153: /**
0154: * Get the cached object.
0155: *
0156: * @param key
0157: * The key for this entry (if null, we won't try to refresh if missing)
0158: * @return The cached object.
0159: */
0160: public Object getPayload(Object key) {
0161: // if we hold null, this is easy
0162: if (m_nullPayload) {
0163: return null;
0164: }
0165:
0166: // for our hard, not soft, option
0167: if (!m_softRefs) {
0168: return m_hardPayload;
0169: }
0170:
0171: // get the payload
0172: Object payload = this .get();
0173:
0174: // if it has been garbage collected, and we can, refresh it
0175: if (payload == null) {
0176: if ((m_refresher != null) && (key != null)) {
0177: // ask the refresher for the value
0178: payload = m_refresher.refresh(key, null, null);
0179:
0180: if (m_memoryService.getCacheLogging()) {
0181: M_log.info("cache miss: refreshing: key: "
0182: + key + " new payload: " + payload);
0183: }
0184:
0185: // store this new value
0186: put(key, payload, m_duration);
0187: } else {
0188: if (m_memoryService.getCacheLogging()) {
0189: M_log.info("cache miss: no refresh: key: "
0190: + key);
0191: }
0192: }
0193: }
0194:
0195: // TODO: stash hard ref in the current LRU...
0196:
0197: return payload;
0198: }
0199:
0200: /**
0201: * Check for expiration.
0202: *
0203: * @return true if expired, false if still good.
0204: */
0205: public boolean hasExpired() {
0206: return ((m_duration > 0) ? (System.currentTimeMillis() > m_expires)
0207: : false);
0208: }
0209:
0210: /**
0211: * Access the duration.
0212: *
0213: * @return The time (seconds) before the entry expires.
0214: */
0215: public int getDuration() {
0216: return m_duration;
0217: }
0218:
0219: /**
0220: * If we have a duration, reset our expiration time.
0221: */
0222: public void reset() {
0223: if (m_duration > 0)
0224: m_expires = System.currentTimeMillis()
0225: + (m_duration * 1000);
0226: }
0227:
0228: } // CacheEntry
0229:
0230: /**
0231: * Construct the Cache. No automatic refresh handling.
0232: */
0233: public MemCache(BasicMemoryService memoryService,
0234: EventTrackingService eventTrackingService) {
0235: // inject our dependencies
0236: m_memoryService = memoryService;
0237: m_eventTrackingService = eventTrackingService;
0238:
0239: m_map = new ConcurrentReaderHashMap();
0240:
0241: // register as a cacher
0242: m_memoryService.registerCacher(this );
0243: }
0244:
0245: /**
0246: * Construct the Cache. Attempts to keep complete on Event notification by calling the refresher.
0247: *
0248: * @param refresher
0249: * The object that will handle refreshing of event notified modified or added entries.
0250: * @param pattern
0251: * The "startsWith()" string for all resources that may be in this cache - if null, don't watch events for updates.
0252: */
0253: public MemCache(BasicMemoryService memoryService,
0254: EventTrackingService eventTrackingService,
0255: CacheRefresher refresher, String pattern) {
0256: this (memoryService, eventTrackingService);
0257: m_refresher = refresher;
0258: m_resourcePattern = pattern;
0259:
0260: // register to get events - first, before others
0261: if (pattern != null) {
0262: m_eventTrackingService.addPriorityObserver(this );
0263: }
0264: }
0265:
0266: /**
0267: * Construct the Cache. Automatic refresh handling if refresher is not null.
0268: *
0269: * @param refresher
0270: * The object that will handle refreshing of expired entries.
0271: * @param sleep
0272: * The number of seconds to sleep between expiration checks.
0273: */
0274: public MemCache(BasicMemoryService memoryService,
0275: EventTrackingService eventTrackingService,
0276: CacheRefresher refresher, long sleep) {
0277: this (memoryService, eventTrackingService);
0278: m_refresherSleep = sleep;
0279:
0280: if (refresher != null) {
0281: m_refresher = refresher;
0282:
0283: // start the expiration thread
0284: start();
0285: }
0286: }
0287:
0288: /**
0289: * Construct the Cache. Event scanning if pattern not null - will expire entries.
0290: *
0291: * @param sleep
0292: * The number of seconds to sleep between expiration checks.
0293: * @param pattern
0294: * The "startsWith()" string for all resources that may be in this cache - if null, don't watch events for expiration.
0295: */
0296: public MemCache(BasicMemoryService memoryService,
0297: EventTrackingService eventTrackingService, long sleep,
0298: String pattern) {
0299: this (memoryService, eventTrackingService);
0300: m_refresherSleep = sleep;
0301: m_resourcePattern = pattern;
0302:
0303: // start the expiration thread
0304: start();
0305:
0306: // register to get events - first, before others
0307: if (pattern != null) {
0308: m_eventTrackingService.addPriorityObserver(this );
0309: }
0310: }
0311:
0312: /**
0313: * Clean up.
0314: */
0315: public void destroy() {
0316: clear();
0317:
0318: // if we are not in a global shutdown
0319: if (!ComponentManager.hasBeenClosed()) {
0320: // remove my registration
0321: m_memoryService.unregisterCacher(this );
0322:
0323: // remove my event notification registration
0324: m_eventTrackingService.deleteObserver(this );
0325: }
0326:
0327: // stop our expiration thread (if any)
0328: stop();
0329: }
0330:
0331: /**
0332: * {@inheritDoc}
0333: */
0334: public void attachDerivedCache(DerivedCache cache) {
0335: // Note: only one is supported
0336: if (cache == null) {
0337: m_derivedCache = null;
0338: } else {
0339: if (m_derivedCache != null) {
0340: M_log.warn("attachDerivedCache - already got one!");
0341: } else {
0342: m_derivedCache = cache;
0343: }
0344: }
0345: }
0346:
0347: /**
0348: * Cache an object
0349: *
0350: * @param key
0351: * The key with which to find the object.
0352: * @param payload
0353: * The object to cache.
0354: * @param duration
0355: * The time to cache the object (seconds).
0356: */
0357: public void put(Object key, Object payload, int duration) {
0358: if (disabled())
0359: return;
0360:
0361: m_map.put(key, new CacheEntry(payload, duration));
0362:
0363: m_putCount++;
0364:
0365: if (m_derivedCache != null)
0366: m_derivedCache.notifyCachePut(key, payload);
0367: }
0368:
0369: /**
0370: * Cache an object - don't automatically exipire it.
0371: *
0372: * @param key
0373: * The key with which to find the object.
0374: * @param payload
0375: * The object to cache.
0376: * @param duration
0377: * The time to cache the object (seconds).
0378: */
0379: public void put(Object key, Object payload) {
0380: put(key, payload, 0);
0381: }
0382:
0383: /**
0384: * Test for an entry in the cache - expired or not.
0385: *
0386: * @param key
0387: * The cache key.
0388: * @return true if the key maps to a cache entry, false if not.
0389: */
0390: public boolean containsKeyExpiredOrNot(Object key) {
0391: if (disabled())
0392: return false;
0393:
0394: // is it there?
0395: boolean rv = m_map.containsKey(key);
0396:
0397: m_getCount++;
0398: if (rv) {
0399: m_hitCount++;
0400: }
0401:
0402: return rv;
0403:
0404: } // containsKeyExpiredOrNot
0405:
0406: /**
0407: * Test for a non expired entry in the cache.
0408: *
0409: * @param key
0410: * The cache key.
0411: * @return true if the key maps to a non-expired cache entry, false if not.
0412: */
0413: public boolean containsKey(Object key) {
0414: if (disabled())
0415: return false;
0416:
0417: m_getCount++;
0418:
0419: // is it there?
0420: CacheEntry entry = (CacheEntry) m_map.get(key);
0421: if (entry != null) {
0422: // has it expired?
0423: if (entry.hasExpired()) {
0424: // if so, remove it
0425: remove(key);
0426: return false;
0427: }
0428: m_hitCount++;
0429: return true;
0430: }
0431:
0432: return false;
0433:
0434: } // containsKey
0435:
0436: /**
0437: * Expire this object.
0438: *
0439: * @param key
0440: * The cache key.
0441: */
0442: public void expire(Object key) {
0443: if (disabled())
0444: return;
0445:
0446: // remove it
0447: remove(key);
0448:
0449: } // expire
0450:
0451: /**
0452: * Get the entry, or null if not there (expired entries are returned, too).
0453: *
0454: * @param key
0455: * The cache key.
0456: * @return The payload, or null if the payload is null, the key is not found. (Note: use containsKey() to remove this ambiguity).
0457: */
0458: public Object getExpiredOrNot(Object key) {
0459: if (disabled())
0460: return null;
0461:
0462: // is it there?
0463: CacheEntry entry = (CacheEntry) m_map.get(key);
0464: if (entry != null) {
0465: return entry.getPayload(key);
0466: }
0467:
0468: return null;
0469:
0470: } // getExpiredOrNot
0471:
0472: /**
0473: * Get the non expired entry, or null if not there (or expired)
0474: *
0475: * @param key
0476: * The cache key.
0477: * @return The payload, or null if the payload is null, the key is not found, or the entry has expired (Note: use containsKey() to remove this ambiguity).
0478: */
0479: public Object get(Object key) {
0480: if (disabled())
0481: return null;
0482:
0483: // get it if there
0484: CacheEntry entry = (CacheEntry) m_map.get(key);
0485: if (entry != null) {
0486: // has it expired?
0487: if (entry.hasExpired()) {
0488: // if so, remove it
0489: remove(key);
0490: return null;
0491: }
0492: return entry.getPayload(key);
0493: }
0494:
0495: return null;
0496:
0497: } // get
0498:
0499: /**
0500: * Get all the non-expired non-null entries.
0501: *
0502: * @return all the non-expired non-null entries, or an empty list if none.
0503: */
0504: public List getAll() {
0505: List rv = new Vector();
0506:
0507: if (disabled())
0508: return rv;
0509: if (m_map.isEmpty())
0510: return rv;
0511:
0512: // for each entry in the cache
0513: for (Iterator iKeys = m_map.entrySet().iterator(); iKeys
0514: .hasNext();) {
0515: Map.Entry e = (Map.Entry) iKeys.next();
0516: CacheEntry entry = (CacheEntry) e.getValue();
0517:
0518: // skip expired
0519: if (entry.hasExpired())
0520: continue;
0521:
0522: Object payload = entry.getPayload(e.getKey());
0523:
0524: // skip nulls
0525: if (payload == null)
0526: continue;
0527:
0528: // skip inappropriate types
0529: // if ((type != null) && (!(type.isInstance(payload)))) continue;
0530:
0531: // filter out those not matching the filter
0532: // if ((filter != null) && (((String) keys[i]).startsWith(filter))) continue;
0533:
0534: // we'll take it
0535: rv.add(payload);
0536: }
0537:
0538: return rv;
0539:
0540: } // getAll
0541:
0542: /**
0543: * Get all the non-expired non-null entries that are in the specified reference path. Note: only works with String keys.
0544: *
0545: * @param path
0546: * The reference path.
0547: * @return all the non-expired non-null entries, or an empty list if none.
0548: */
0549: public List getAll(String path) {
0550: List rv = new Vector();
0551:
0552: if (disabled())
0553: return rv;
0554: if (m_map.isEmpty())
0555: return rv;
0556:
0557: // for each entry in the cache
0558: for (Iterator iKeys = m_map.entrySet().iterator(); iKeys
0559: .hasNext();) {
0560: Map.Entry e = (Map.Entry) iKeys.next();
0561: CacheEntry entry = (CacheEntry) e.getValue();
0562:
0563: // skip expired
0564: if (entry.hasExpired())
0565: continue;
0566:
0567: Object payload = entry.getPayload(e.getKey());
0568:
0569: // skip nulls
0570: if (payload == null)
0571: continue;
0572:
0573: // take only if keys start with path, and have no SEPARATOR following other than at the end %%%
0574: String keyPath = referencePath((String) e.getKey());
0575: if (!keyPath.equals(path))
0576: continue;
0577:
0578: // we'll take it
0579: rv.add(payload);
0580: }
0581:
0582: return rv;
0583:
0584: } // getAll
0585:
0586: /**
0587: * Get all the keys
0588: *
0589: * @return The List of key values (Object).
0590: */
0591: public List getKeys() {
0592: List rv = new Vector();
0593: rv.addAll(m_map.keySet());
0594: return rv;
0595:
0596: } // getKeys
0597:
0598: /**
0599: * Get all the keys, eache modified to remove the resourcePattern prefix. Note: only works with String keys.
0600: *
0601: * @return The List of keys converted from references to ids (String).
0602: */
0603: public List getIds() {
0604: List rv = new Vector();
0605:
0606: for (Iterator it = m_map.keySet().iterator(); it.hasNext();) {
0607: String key = (String) it.next();
0608: int i = key.indexOf(m_resourcePattern);
0609: if (i != -1)
0610: key = key.substring(i + m_resourcePattern.length());
0611: rv.add(key);
0612: }
0613:
0614: return rv;
0615:
0616: } // getKeys
0617:
0618: /**
0619: * Clear all entries.
0620: */
0621: public void clear() {
0622: m_map.clear();
0623: m_complete = false;
0624: m_partiallyComplete.clear();
0625: m_getCount = 0;
0626: m_hitCount = 0;
0627: m_putCount = 0;
0628:
0629: if (m_derivedCache != null)
0630: m_derivedCache.notifyCacheClear();
0631:
0632: } // clear
0633:
0634: /**
0635: * Remove this entry from the cache.
0636: *
0637: * @param key
0638: * The cache key.
0639: */
0640: public void remove(Object key) {
0641: if (disabled())
0642: return;
0643:
0644: CacheEntry entry = (CacheEntry) m_map.remove(key);
0645:
0646: if (m_derivedCache != null) {
0647: Object old = null;
0648: if (entry != null) {
0649: old = entry.getHardPayload();
0650: }
0651:
0652: m_derivedCache.notifyCacheRemove(key, old);
0653: }
0654:
0655: } // remove
0656:
0657: /**
0658: * Disable the cache.
0659: */
0660: public void disable() {
0661: m_disabled = true;
0662: m_eventTrackingService.deleteObserver(this );
0663: clear();
0664:
0665: } // disable
0666:
0667: /**
0668: * Enable the cache.
0669: */
0670: public void enable() {
0671: m_disabled = false;
0672:
0673: if (m_resourcePattern != null) {
0674: m_eventTrackingService.addPriorityObserver(this );
0675: }
0676:
0677: } // enable
0678:
0679: /**
0680: * Is the cache disabled?
0681: *
0682: * @return true if the cache is disabled, false if it is enabled.
0683: */
0684: public boolean disabled() {
0685: return m_disabled;
0686:
0687: } // disabled
0688:
0689: /**
0690: * Are we complete?
0691: *
0692: * @return true if we have all the possible entries cached, false if not.
0693: */
0694: public boolean isComplete() {
0695: if (disabled())
0696: return false;
0697:
0698: return m_complete;
0699:
0700: } // isComplete
0701:
0702: /**
0703: * Set the cache to be complete, containing all possible entries.
0704: */
0705: public void setComplete() {
0706: if (disabled())
0707: return;
0708:
0709: m_complete = true;
0710:
0711: } // isComplete
0712:
0713: /**
0714: * Are we complete for one level of the reference hierarchy?
0715: *
0716: * @param path
0717: * The reference to the completion level.
0718: * @return true if we have all the possible entries cached, false if not.
0719: */
0720: public boolean isComplete(String path) {
0721: return m_partiallyComplete.contains(path);
0722:
0723: } // isComplete
0724:
0725: /**
0726: * Set the cache to be complete for one level of the reference hierarchy.
0727: *
0728: * @param path
0729: * The reference to the completion level.
0730: */
0731: public void setComplete(String path) {
0732: m_partiallyComplete.add(path);
0733:
0734: } // setComplete
0735:
0736: /**
0737: * Set the cache to hold events for later processing to assure an atomic "complete" load.
0738: */
0739: public void holdEvents() {
0740: m_holdEventProcessing = true;
0741:
0742: } // holdEvents
0743:
0744: /**
0745: * Restore normal event processing in the cache, and process any held events now.
0746: */
0747: public void processEvents() {
0748: m_holdEventProcessing = false;
0749:
0750: for (int i = 0; i < m_heldEvents.size(); i++) {
0751: Event event = (Event) m_heldEvents.get(i);
0752: continueUpdate(event);
0753: }
0754:
0755: m_heldEvents.clear();
0756:
0757: } // holdEvents
0758:
0759: /**********************************************************************************************************************************************************************************************************************************************************
0760: * Cacher implementation
0761: *********************************************************************************************************************************************************************************************************************************************************/
0762:
0763: /**
0764: * Clear out as much as possible anything cached; re-sync any cache that is needed to be kept.
0765: */
0766: public void resetCache() {
0767: clear();
0768:
0769: } // resetCache
0770:
0771: /**
0772: * Return the size of the cacher - indicating how much memory in use.
0773: *
0774: * @return The size of the cacher.
0775: */
0776: public long getSize() {
0777: return m_map.size();
0778: }
0779:
0780: /**
0781: * Return a description of the cacher.
0782: *
0783: * @return The cacher's description.
0784: */
0785: public String getDescription() {
0786: StringBuffer buf = new StringBuffer();
0787: buf.append("MemCache");
0788: if (m_softRefs) {
0789: buf.append(" soft");
0790: }
0791: if (m_disabled) {
0792: buf.append(" disabled");
0793: }
0794: if (m_complete) {
0795: buf.append(" complete");
0796: }
0797: if (m_resourcePattern != null) {
0798: buf.append(" " + m_resourcePattern);
0799: }
0800: if (m_refresher != null) {
0801: buf.append(" " + m_refresher.toString());
0802: }
0803: if (m_thread != null) {
0804: buf.append(" thread_sleep: " + m_refresherSleep);
0805: }
0806: if (m_partiallyComplete.size() > 0) {
0807: buf.append(" partially_complete[");
0808: for (Iterator i = m_partiallyComplete.iterator(); i
0809: .hasNext();) {
0810: buf.append(" " + i.next());
0811: }
0812: buf.append("]");
0813: }
0814:
0815: buf.append(" puts:"
0816: + m_putCount
0817: + " gets:"
0818: + m_getCount
0819: + " hits:"
0820: + m_hitCount
0821: + " hit%:"
0822: + ((m_getCount > 0) ? ""
0823: + ((100l * m_hitCount) / m_getCount) : "n/a"));
0824:
0825: return buf.toString();
0826: }
0827:
0828: /**********************************************************************************************************************************************************************************************************************************************************
0829: * Runnable implementation
0830: *********************************************************************************************************************************************************************************************************************************************************/
0831:
0832: /** The thread which runs the expiration check. */
0833: protected Thread m_thread = null;
0834:
0835: /** My thread's quit flag. */
0836: protected boolean m_threadStop = false;
0837:
0838: /**
0839: * Start the expiration thread.
0840: */
0841: protected void start() {
0842: m_threadStop = false;
0843:
0844: m_thread = new Thread(this , getClass().getName());
0845: m_thread.setDaemon(true);
0846: m_thread.setPriority(Thread.MIN_PRIORITY + 2);
0847: m_thread.start();
0848:
0849: } // start
0850:
0851: /**
0852: * Stop the expiration thread.
0853: */
0854: protected void stop() {
0855: if (m_thread == null)
0856: return;
0857:
0858: // signal the thread to stop
0859: m_threadStop = true;
0860:
0861: // wake up the thread
0862: m_thread.interrupt();
0863:
0864: m_thread = null;
0865:
0866: } // stop
0867:
0868: /**
0869: * Run the expiration thread.
0870: */
0871: public void run() {
0872: // since we might be running while the component manager is still being created and populated, such as at server
0873: // startup, wait here for a complete component manager
0874: ComponentManager.waitTillConfigured();
0875:
0876: // loop till told to stop
0877: while ((!m_threadStop)
0878: && (!Thread.currentThread().isInterrupted())) {
0879: long startTime = 0;
0880: try {
0881: if (M_log.isDebugEnabled()) {
0882: startTime = System.currentTimeMillis();
0883: M_log.debug(this + ".checking ...");
0884: }
0885:
0886: // collect keys of expired entries in the cache
0887: List expired = new Vector();
0888: for (Iterator iKeys = m_map.entrySet().iterator(); iKeys
0889: .hasNext();) {
0890: Map.Entry e = (Map.Entry) iKeys.next();
0891: String key = (String) e.getKey();
0892: CacheEntry entry = (CacheEntry) e.getValue();
0893:
0894: // if it has expired
0895: if (entry.hasExpired()) {
0896: expired.add(key);
0897: }
0898: }
0899:
0900: // if we have a refresher, for each expired, try to refresh
0901: if (m_refresher != null) {
0902: for (Iterator iKeys = expired.iterator(); iKeys
0903: .hasNext();) {
0904: String key = (String) iKeys.next();
0905: CacheEntry entry = (CacheEntry) m_map.get(key);
0906: if (entry != null) {
0907: Object newValue = m_refresher.refresh(key,
0908: entry.getPayload(null), null);
0909:
0910: remove(key);
0911:
0912: // if the response is not null, replace and rejuvinate
0913: if (newValue != null) {
0914: put(key, newValue, entry.getDuration());
0915: }
0916: }
0917: }
0918: }
0919:
0920: // if no refresher, for each expired, remove
0921: else {
0922: for (Iterator iKeys = expired.iterator(); iKeys
0923: .hasNext();) {
0924: String key = (String) iKeys.next();
0925: remove(key);
0926: }
0927: }
0928: } catch (Throwable e) {
0929: M_log.warn(this + ": exception: ", e);
0930: }
0931:
0932: if (M_log.isDebugEnabled()) {
0933: M_log.debug(this + ".done. Time: "
0934: + (System.currentTimeMillis() - startTime));
0935: }
0936:
0937: // take a small nap
0938: try {
0939: Thread.sleep(m_refresherSleep * 1000);
0940: } catch (Exception ignore) {
0941: }
0942:
0943: } // while
0944:
0945: } // run
0946:
0947: /**********************************************************************************************************************************************************************************************************************************************************
0948: * Observer implementation
0949: *********************************************************************************************************************************************************************************************************************************************************/
0950:
0951: /**
0952: * This method is called whenever the observed object is changed. An application calls an <tt>Observable</tt> object's <code>notifyObservers</code> method to have all the object's observers notified of the change. default implementation is to
0953: * cause the courier service to deliver to the interface controlled by my controller. Extensions can override.
0954: *
0955: * @param o
0956: * the observable object.
0957: * @param arg
0958: * an argument passed to the <code>notifyObservers</code> method.
0959: */
0960: public void update(Observable o, Object arg) {
0961: if (disabled())
0962: return;
0963:
0964: // arg is Event
0965: if (!(arg instanceof Event))
0966: return;
0967: Event event = (Event) arg;
0968:
0969: // if this is just a read, not a modify event, we can ignore it
0970: if (!event.getModify())
0971: return;
0972:
0973: String key = event.getResource();
0974:
0975: // if this resource is not in my pattern of resources, we can ignore it
0976: if (!key.startsWith(m_resourcePattern))
0977: return;
0978:
0979: // if we are holding event processing
0980: if (m_holdEventProcessing) {
0981: m_heldEvents.add(event);
0982: return;
0983: }
0984:
0985: continueUpdate(event);
0986:
0987: } // update
0988:
0989: /**
0990: * Complete the update, given an event that we know we need to act upon.
0991: *
0992: * @param event
0993: * The event to process.
0994: */
0995: protected void continueUpdate(Event event) {
0996: String key = event.getResource();
0997:
0998: if (M_log.isDebugEnabled())
0999: M_log.debug(this + ".update() [" + m_resourcePattern
1000: + "] resource: " + key + " event: "
1001: + event.getEvent());
1002:
1003: // do we have this in our cache?
1004: Object oldValue = get(key);
1005: if (m_map.containsKey(key)) {
1006: // invalidate our copy
1007: remove(key);
1008: }
1009:
1010: // if we are being complete, we need to get this cached.
1011: if (m_complete) {
1012: // we can only get it cached if we have a refresher
1013: if (m_refresher != null) {
1014: // ask the refresher for the value
1015: Object value = m_refresher
1016: .refresh(key, oldValue, event);
1017: if (value != null) {
1018: put(key, value);
1019: }
1020: } else {
1021: // we can no longer claim to be complete
1022: m_complete = false;
1023: }
1024: }
1025:
1026: // if we are partially complete
1027: else if (!m_partiallyComplete.isEmpty()) {
1028: // what is the reference path that this key lives within?
1029: String path = referencePath(key);
1030:
1031: // if we are partially complete for this path
1032: if (m_partiallyComplete.contains(path)) {
1033: // we can only get it cached if we have a refresher
1034: if (m_refresher != null) {
1035: // ask the refresher for the value
1036: Object value = m_refresher.refresh(key, oldValue,
1037: event);
1038: if (value != null) {
1039: put(key, value);
1040: }
1041: } else {
1042: // we can no longer claim to be complete for this path
1043: m_partiallyComplete.remove(path);
1044: }
1045: }
1046: }
1047:
1048: } // continueUpdate
1049:
1050: /**
1051: * Compute the reference path (i.e. the container) for a given reference.
1052: *
1053: * @param ref
1054: * The reference string.
1055: * @return The reference root for the given reference.
1056: */
1057: protected String referencePath(String ref) {
1058: String path = null;
1059:
1060: // Note: there may be a trailing separator
1061: int pos = ref.lastIndexOf("/", ref.length() - 2);
1062:
1063: // if no separators are found, place it even before the root!
1064: if (pos == -1) {
1065: path = "";
1066: }
1067:
1068: // use the string up to and including that last separator
1069: else {
1070: path = ref.substring(0, pos + 1);
1071: }
1072:
1073: return path;
1074:
1075: } // referencePath
1076: }
|