001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.concurrency.caching;
007:
008: import java.util.Date;
009:
010: import org.jasig.portal.EntityIdentifier;
011: import org.jasig.portal.IBasicEntity;
012: import org.jasig.portal.concurrency.CachingException;
013: import org.apache.commons.logging.Log;
014: import org.apache.commons.logging.LogFactory;
015: import org.jasig.portal.services.SequenceGenerator;
016:
017: /**
018: * Reference implementation of <code>IEntityCache</code> that is meant
019: * for a multi-server environment in which updates to cached entities may
020: * occur on peer caches on other JVMs, invalidating the local copy of the
021: * entity.
022: * <p>
023: * Cache entries are wrapped in a <code>CacheEntry</code> that records
024: * their creation time. At intervals, cleanupCache() is called by the
025: * cache's cleanup thread. When this happens, the class retrieves
026: * invalidation notices from its invalidation store and purges stale entries.
027: * <p>
028: * A fudge factor (clockTolerance) is employed to account for differences
029: * in system clocks among servers. This may cause a valid entry to be
030: * removed from the cache if it is newer than the corresponding
031: * invalidation by less than the fudge factor. However, this should
032: * not be to frequent, and assuming the factor is appropriately set,
033: * all relevant invalidations should occur.
034: * <p>
035: * @author Dan Ellentuck
036: * @version $Revision: 36058 $
037: */
038: public class ReferenceInvalidatingEntityCache extends
039: ReferenceEntityCache {
040: private static final Log LOG = LogFactory
041: .getLog(ReferenceInvalidatingEntityCache.class);
042: private static RDBMCachedEntityInvalidationStore invalidationStore;
043: private long lastUpdateMillis = 0;
044: private long clockTolerance = 5000;
045: private int cacheID = -1;
046: private static final String CACHE_ID_SEQUENCE = "UP_ENTITY_CACHE";
047:
048: // Wrapper records the time a cache entry was created.
049: class CacheEntry implements IBasicEntity {
050: protected IBasicEntity ent;
051: protected Date creationTime = new Date();
052:
053: protected CacheEntry(IBasicEntity entity) {
054: super ();
055: ent = entity;
056: }
057:
058: public EntityIdentifier getEntityIdentifier() {
059: return ent.getEntityIdentifier();
060: }
061:
062: public Class getType() {
063: return getEntityIdentifier().getType();
064: }
065:
066: public String getKey() {
067: return getEntityIdentifier().getKey();
068: }
069:
070: public IBasicEntity getEntity() {
071: return ent;
072: }
073:
074: public Date getCreationTime() {
075: return creationTime;
076: }
077: }
078:
079: /**
080: * ReferenceInvalidatingEntityCache constructor comment.
081: */
082: public ReferenceInvalidatingEntityCache(Class type, int maxSize,
083: int maxUnusedTime, int sweepInterval, int clock)
084: throws CachingException {
085: super (type, maxSize, maxUnusedTime, sweepInterval);
086: clockTolerance = clock;
087: initializeCacheID();
088: }
089:
090: /**
091: * ReferenceInvalidatingEntityCache constructor comment.
092: */
093: public ReferenceInvalidatingEntityCache(Class type, int maxSize,
094: int maxUnusedTime, int sweepInterval)
095: throws CachingException {
096: super (type, maxSize, maxUnusedTime, sweepInterval);
097: }
098:
099: /**
100: * Wrap the incoming entity and add to the cache.
101: * @param entity the entity to be added to the cache.
102: */
103: public void add(IBasicEntity entity) throws CachingException {
104: super .add(new CacheEntry(entity));
105: }
106:
107: /**
108: * Remove stale entries from the cache.
109: */
110: public void cleanupCache() {
111: String msg = null;
112: long start = 0, end = 0;
113: java.sql.Timestamp ts;
114:
115: start = System.currentTimeMillis();
116: debug("ENTERING " + this + " cleanupCache() ");
117:
118: if (!getCache().isEmpty()) {
119: removeInvalidEntities();
120: super .cleanupCache();
121: }
122:
123: end = System.currentTimeMillis();
124: msg = "LEAVING " + this + " cleanupCache(); total time: "
125: + (end - start) + "ms";
126: debug(msg);
127: }
128:
129: /**
130: * May want to do something with the invalidator thread.
131: * @throws Throwable
132: */
133: protected void finalize() throws Throwable {
134: super .finalize();
135: }
136:
137: /**
138: * Unwraps and returns the cached entity.
139: * @param key - the key of the entity.
140: * @return org.jasig.portal.IBasicEntity
141: */
142: public IBasicEntity get(String key) {
143: CacheEntry entry = (CacheEntry) primGet(key);
144: return (entry != null) ? entry.getEntity() : null;
145: }
146:
147: /**
148: * @return org.jasig.portal.concurrency.caching.RDBMCachedEntityInvalidationStore
149: */
150: private static synchronized RDBMCachedEntityInvalidationStore getInvalidationStore()
151: throws CachingException {
152: if (invalidationStore == null) {
153: invalidationStore = new RDBMCachedEntityInvalidationStore();
154: }
155: return invalidationStore;
156: }
157:
158: /**
159: * @param entity org.jasig.portal.IBasicEntity
160: */
161: public void invalidate(IBasicEntity entity) throws CachingException {
162: CachedEntityInvalidation cei = new CachedEntityInvalidation(
163: entity.getEntityIdentifier(), new Date(), getCacheID());
164: getInvalidationStore().add(cei);
165: }
166:
167: /**
168: * Returns the WRAPPED cached entity.
169: * @param key - the key of the entity.
170: * @return org.jasig.portal.IBasicEntity
171: */
172: private IBasicEntity primGet(String key) {
173: return super .get(key);
174: }
175:
176: /**
177: * @param key the entity to be un-cached.
178: */
179: private void primRemove(String key) throws CachingException {
180: super .remove(key);
181: }
182:
183: /**
184: * @param key - the key of the entity to be un-cached.
185: */
186: public void remove(String key) throws CachingException {
187: IBasicEntity ent = get(key);
188: if (ent != null) {
189: invalidate(ent);
190: primRemove(key);
191: }
192: }
193:
194: /**
195: * Retrieves invalidations that were added to the store by other caches
196: * since the last time we checked (fudged with the clockTolerance).
197: * If a cache entry exists for the invalidation, and the entry is older
198: * than the invalidation (again, fudged with the clockTolerance), then
199: * the entry is removed.
200: *
201: * This may drop a few perfectly valid entries from the cache that
202: * are newer than the corresponding invalidation by less than the
203: * fudge factor. However, assuming the factor is appropriately set,
204: * all relevant invalidations should occur.
205: *
206: */
207: public void removeInvalidEntities() {
208: CachedEntityInvalidation[] invalidations = null;
209: long nowMillis = System.currentTimeMillis();
210: Date lastUpdate = new Date(lastUpdateMillis - clockTolerance);
211: int removed = 0;
212:
213: debug("ReferenceInvalidatingEntityCache.removeInvalidEntries(): "
214: + getEntityType()
215: + " checking for cache invalidations added since: "
216: + lastUpdate);
217: try {
218: Integer cID = new Integer(getCacheID());
219: invalidations = getInvalidationStore().findAfter(
220: lastUpdate, getEntityType(), null, cID);
221:
222: debug("ReferenceInvalidatingEntityCache.removeInvalidEntries(): "
223: + getEntityType()
224: + " retrieved "
225: + invalidations.length + " invalidations.");
226:
227: for (int i = 0; i < invalidations.length; i++) {
228: String key = invalidations[i].getKey();
229: CacheEntry entry = (CacheEntry) primGet(key);
230: if (entry != null) {
231: long entryCreationTime = entry.getCreationTime()
232: .getTime();
233: long invalidationTime = invalidations[i]
234: .getInvalidationTime().getTime();
235:
236: if (entryCreationTime < invalidationTime
237: + clockTolerance) {
238: primRemove(key);
239: removed++;
240: }
241: }
242: }
243:
244: debug("ReferenceInvalidatingEntityCache.removeInvalidEntries(): "
245: + getEntityType()
246: + " removed "
247: + removed
248: + " cache entries.");
249: } catch (Exception ex) {
250: LOG.error(ex.getMessage(), ex);
251: }
252:
253: lastUpdateMillis = nowMillis;
254: }
255:
256: /**
257: * Returns a String that represents the value of this object.
258: * @return a string representation of the receiver
259: */
260: public String toString() {
261: return "ReferenceInvalidatingEntityCache for "
262: + getEntityType().getName();
263: }
264:
265: /**
266: * First invalidate, then cache the incoming entity.
267: * @param entity the entity to be updated in the cache.
268: */
269: public void update(IBasicEntity entity) throws CachingException {
270: invalidate(entity);
271: add(entity);
272: }
273:
274: /**
275: * @return int
276: */
277: public int getCacheID() {
278: return cacheID;
279: }
280:
281: private void initializeCacheID() throws CachingException {
282: try {
283: cacheID = SequenceGenerator.instance().getNextInt(
284: CACHE_ID_SEQUENCE);
285: } catch (Exception ex) {
286: LOG.error(ex.getMessage(), ex);
287: throw new CachingException(ex);
288: }
289: }
290:
291: }
|