/* * JBoss, Home of Professional Open Source * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.seam.wiki.connectors.cache; import org.jboss.seam.ScopeType; import org.jboss.seam.Component; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Synchronized; import org.jboss.seam.log.Log; import java.util.*; /** * Caches lists of stuff and can asynchronously call a connector, upon configurable cache timeout, to * refresh these lists. * <p> * All method calls to this application-scoped component are synchronized by Seam. That means you might * get an <tt>IllegalStateException</tt> when an exclusive lock couldn't be aquired by a thread (because * some other thread was already accessing this instance). It's up to the caller to handle that exception, * usually you'd log and swallow it and continue without the result of the cache. * </p> * * @author Christian Bauer */ @Scope(ScopeType.APPLICATION) @Synchronized(timeout = 5000) public abstract class ConnectorCache<T, K> { @Logger protected Log log; private Map<ConnectorCacheKey<K>, List<T>> cache = new HashMap<ConnectorCacheKey<K>, List<T>>(); protected List<T> lookup(ConnectorCacheKey<K> key) { long currentTime = System.currentTimeMillis(); ConnectorCacheKey<K> cacheKey = findKey(key); List<T> result = Collections.EMPTY_LIST; if (cacheKey == null) { log.debug("cache miss, retrieving it from connector, asynchronously: " + isFirstCacheMissResolvedAsynchronously()); // The following operations modify the structure of the cache map, which is ok because this // method is synchronized by Seam. The write triggered (later) by the AysncUpdater is not // synchronized, but we never modify the structure of the cache map then, only now. if (getAsyncUpdater() != null && isFirstCacheMissResolvedAsynchronously()) { // Write an empty list into the cache write(key, Collections.EMPTY_LIST, currentTime); // Now start the asynchronous update getAsyncUpdater().updateCacheAsynchronously(this, key); } else { // If we don't have it cached, the (probably first) caller needs to wait (not asynchronous) until we are done result = udpateCacheSynchronously(this, key); write(key, result, currentTime); } } else { log.debug("cache hit"); if (getUpdateTimeoutSeconds() != 0) { log.debug("checking age of cached entry against update timeout"); // Check updateTimestamp of cached entry if (currentTime - cacheKey.getUpdateTimestamp() > (getUpdateTimeoutSeconds()*1000) ) { log.debug("cached entry is older than maximum cache time, refreshing..."); // Start asynchronous updating, might take a while - but should never take longer than cache timeout! getAsyncUpdater().updateCacheAsynchronously(this, cacheKey); // Meanwhile, update the timestamp so that the next caller doesn't also start asynchronous updating // .. we expect to be finished with that before the next caller runs into a cache timeout again! cacheKey.setAccessTimestamp(currentTime); cacheKey.setUpdateTimestamp(currentTime); } else { log.debug("cached entry is still inside maximum cache time"); } } // Read the value from the cache result = read(cacheKey, currentTime); } // Remove anything with too old accessTimestamp purge(currentTime); return result; } protected void write(ConnectorCacheKey<K> key, List<T> list, long currentTime) { key.setUpdateTimestamp(currentTime); key.setAccessTimestamp(currentTime); cache.put(key, list); } protected List<T> read(ConnectorCacheKey<K> key, long currentTime) { key.setAccessTimestamp(currentTime); return cache.get(key); } protected void purge(long currentTime) { Set<ConnectorCacheKey<K>> outdatedConnectorCacheKeys = new HashSet<ConnectorCacheKey<K>>(); Set<ConnectorCacheKey<K>> keyset = cache.keySet(); for (ConnectorCacheKey key : keyset) { if (currentTime - key.getAccessTimestamp() > (getIdleTimeoutSeconds()*1000)) { log.debug("removing old cache entry, last accessed: " + key.getAccessTimestamp()); outdatedConnectorCacheKeys.add(key); } } for (ConnectorCacheKey outdatedConnectorCacheKey : outdatedConnectorCacheKeys) cache.remove(outdatedConnectorCacheKey); } protected ConnectorCacheAsyncUpdater<T, K> getAsyncUpdater() { return (ConnectorCacheAsyncUpdater<T, K>) Component.getInstance(getAsyncUpdaterClass()); } protected ConnectorCacheKey<K> findKey(ConnectorCacheKey<K> key) { for (ConnectorCacheKey keyOfMap : cache.keySet()) { if (keyOfMap.equals(key)) return keyOfMap; } return null; } protected long getUpdateTimeoutSeconds() { return 0; } protected abstract long getIdleTimeoutSeconds(); protected Class<? extends ConnectorCacheAsyncUpdater<T, K>> getAsyncUpdaterClass() { return null; } protected boolean isFirstCacheMissResolvedAsynchronously() { return true; } protected List<T> udpateCacheSynchronously(ConnectorCache<T, K> cache, ConnectorCacheKey<K> key) { return Collections.EMPTY_LIST; } }