Commits

Anonymous committed 82a8f6a

Submitted by: Andres March

Remove AbstractConcurrentReadCache and replace with a ConcurrentHashMap backed impl of the Cache interface. First check-in. TestCompleteBase passes but there are many things still lacking. DiskPersistence is just one of them.

  • Participants
  • Parent commits 1a3c839
  • Branches amarch_sandbox

Comments (0)

Files changed (21)

 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src/core/java"/>
-	<classpathentry output="build.test" kind="src" path="src/core/test"/>
-	<classpathentry kind="src" path="src/plugins/clustersupport/java"/>
-	<classpathentry output="build.test" kind="src" path="src/plugins/clustersupport/test"/>
-	<classpathentry kind="src" path="src/plugins/diskpersistence/java"/>
-	<classpathentry output="build.test" kind="src" path="src/plugins/diskpersistence/test"/>
-	<classpathentry kind="lib" path="lib/build/clover-1.2.3.jar"/>
-	<classpathentry kind="lib" path="lib/build/commons-net-1.0.0.jar"/>
-	<classpathentry kind="lib" path="lib/build/httpunit.jar"/>
-	<classpathentry kind="lib" path="lib/build/jms.jar"/>
-	<classpathentry kind="lib" path="lib/build/junit-3.8.1.jar"/>
-	<classpathentry kind="lib" path="lib/build/junitperf.jar"/>
-	<classpathentry kind="lib" path="lib/build/optional.jar"/>
-	<classpathentry kind="lib" path="lib/build/servlet.jar"/>
-	<classpathentry kind="lib" path="lib/core/commons-logging.jar"/>
-	<classpathentry kind="lib" path="lib/plugins/clustersupport/jgroups-2.2.7.jar"/>
-	<classpathentry kind="lib" path="lib/core/commons-collections-3.1.jar"/>
-	<classpathentry kind="lib" path="lib/build/ant-junit.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="JUNIT_HOME/junit.jar"/>
+	<classpathentry kind="src" path="src/core/java">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry output="build.test" kind="src" path="src/core/test">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="src/plugins/clustersupport/java">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry output="build.test" kind="src" path="src/plugins/clustersupport/test">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="src/plugins/diskpersistence/java">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry output="build.test" kind="src" path="src/plugins/diskpersistence/test">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/clover-1.2.3.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/commons-net-1.0.0.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/httpunit.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/jms.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/junit-3.8.1.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/junitperf.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/optional.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/servlet.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/core/commons-logging.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/plugins/clustersupport/jgroups-2.2.7.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/core/commons-collections-3.1.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/build/ant-junit.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="var" path="JUNIT_HOME/junit.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/core/backport-util-concurrent.jar">
+		<attributes>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="build"/>
 </classpath>

File lib/core/backport-util-concurrent.jar

Binary file added.

File src/core/java/com/opensymphony/oscache/base/AbstractCacheAdministrator.java

             return;
         }
 
-        Object[] listeners = cache.listenerList.getListenerList();
+        Object[] listeners = cache.getListenerList().getListenerList();
 
         for (int i = listeners.length - 2; i >= 0; i -= 2) {
             if (listeners[i + 1] instanceof LifecycleAware) {

File src/core/java/com/opensymphony/oscache/base/Cache.java

  */
 package com.opensymphony.oscache.base;
 
-import com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache;
-import com.opensymphony.oscache.base.algorithm.LRUCache;
 import com.opensymphony.oscache.base.algorithm.UnlimitedCache;
-import com.opensymphony.oscache.base.events.*;
+import com.opensymphony.oscache.base.events.CacheEventListener;
 import com.opensymphony.oscache.base.persistence.PersistenceListener;
-import com.opensymphony.oscache.util.FastCronParser;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.io.Serializable;
-
-import java.text.ParseException;
-
-import java.util.*;
+import java.util.Date;
 
 import javax.swing.event.EventListenerList;
 
 /**
- * Provides an interface to the cache itself. Creating an instance of this class
- * will create a cache that behaves according to its construction parameters.
- * The public API provides methods to manage objects in the cache and configure
- * any cache event listeners.
+ * DOCUMENT ME!
  *
- * @version        $Revision$
- * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
- * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
- * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
- * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
+ * @author $author$
+ * @version $Revision$
  */
-public class Cache implements Serializable {
+public interface Cache {
     /**
      * An event that origininated from within another event.
      */
     public static final String NESTED_EVENT = "NESTED";
-    private static transient final Log log = LogFactory.getLog(Cache.class);
-
-    /**
-     * A list of all registered event listeners for this cache.
-     */
-    protected EventListenerList listenerList = new EventListenerList();
-
-    /**
-     * The actual cache map. This is where the cached objects are held.
-     */
-    private AbstractConcurrentReadCache cacheMap = null;
-
-    /**
-     * Date of last complete cache flush.
-     */
-    private Date flushDateTime = null;
-
-    /**
-     * A set that holds keys of cache entries that are currently being built.
-     * The cache checks against this map when a stale entry is requested.
-     * If the requested key is in here, we know the entry is currently being
-     * built by another thread and hence we can either block and wait or serve
-     * the stale entry (depending on whether cache blocking is enabled or not).
-     * <p>
-     * We need to isolate these here since the actual CacheEntry
-     * objects may not normally be held in memory at all (eg, if no
-     * memory cache is configured).
-     */
-    private Map updateStates = new HashMap();
-
-    /**
-     * Indicates whether the cache blocks requests until new content has
-     * been generated or just serves stale content instead.
-     */
-    private boolean blocking = false;
-
-    /**
-     * Create a new Cache
-     *
-     * @param useMemoryCaching Specify if the memory caching is going to be used
-     * @param unlimitedDiskCache Specify if the disk caching is unlimited
-     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
-     */
-    public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) {
-        this(useMemoryCaching, unlimitedDiskCache, overflowPersistence, false, null, 0);
-    }
-
-    /**
-     * Create a new Cache.
-     *
-     * If a valid algorithm class is specified, it will be used for this cache.
-     * Otherwise if a capacity is specified, it will use LRUCache.
-     * If no algorithm or capacity is specified UnlimitedCache is used.
-     *
-     * @see com.opensymphony.oscache.base.algorithm.LRUCache
-     * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
-     * @param useMemoryCaching Specify if the memory caching is going to be used
-     * @param unlimitedDiskCache Specify if the disk caching is unlimited
-     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
-     * @param blocking This parameter takes effect when a cache entry has
-     * just expired and several simultaneous requests try to retrieve it. While
-     * one request is rebuilding the content, the other requests will either
-     * block and wait for the new content (<code>blocking == true</code>) or
-     * instead receive a copy of the stale content so they don't have to wait
-     * (<code>blocking == false</code>). the default is <code>false</code>,
-     * which provides better performance but at the expense of slightly stale
-     * data being served.
-     * @param algorithmClass The class implementing the desired algorithm
-     * @param capacity The capacity
-     */
-    public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String algorithmClass, int capacity) {
-        // Instantiate the algo class if valid
-        if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
-            try {
-                cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
-                cacheMap.setMaxEntries(capacity);
-            } catch (Exception e) {
-                log.error("Invalid class name for cache algorithm class. " + e.toString());
-            }
-        }
-
-        if (cacheMap == null) {
-            // If we have a capacity, use LRU cache otherwise use unlimited Cache
-            if (capacity > 0) {
-                cacheMap = new LRUCache(capacity);
-            } else {
-                cacheMap = new UnlimitedCache();
-            }
-        }
-
-        cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
-        cacheMap.setOverflowPersistence(overflowPersistence);
-        cacheMap.setMemoryCaching(useMemoryCaching);
-
-        this.blocking = blocking;
-    }
 
     /**
      * Allows the capacity of the cache to be altered dynamically. Note that
      *
      * @param capacity the maximum number of items to hold in the cache.
      */
-    public void setCapacity(int capacity) {
-        cacheMap.setMaxEntries(capacity);
-    }
+    public abstract void setCapacity(int capacity);
 
     /**
      * Checks if the cache was flushed more recently than the CacheEntry provided.
      * @param cacheEntry The cache entry which we're seeing whether to refresh
      * @return Whether or not the cache has been flushed more recently than this cache entry was updated.
      */
-    public boolean isFlushed(CacheEntry cacheEntry) {
-        if (flushDateTime != null) {
-            long lastUpdate = cacheEntry.getLastUpdate();
-
-            return (flushDateTime.getTime() >= lastUpdate);
-        } else {
-            return false;
-        }
-    }
+    public abstract boolean isFlushed(CacheEntry cacheEntry);
 
     /**
      * Retrieve an object from the cache specifying its key.
      * to repopulate the cache, they <em>must</em> instead call
      * {@link #cancelUpdate(String)}.
      */
-    public Object getFromCache(String key) throws NeedsRefreshException {
-        return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
-    }
+    public abstract Object getFromCache(String key) throws NeedsRefreshException;
 
     /**
      * Retrieve an object from the cache specifying its key.
      * to repopulate the cache, they <em>must</em> instead call
      * {@link #cancelUpdate(String)}.
      */
-    public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
-        return getFromCache(key, refreshPeriod, null);
-    }
+    public abstract Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException;
 
     /**
      * Retrieve an object from the cache specifying its key.
      * to repopulate the cache, they <em>must</em> instead call
      * {@link #cancelUpdate(String)}.
      */
-    public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
-        CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
-
-        Object content = cacheEntry.getContent();
-        CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
-
-        boolean reload = false;
-
-        // Check if this entry has expired or has not yet been added to the cache. If
-        // so, we need to decide whether to block, serve stale content or throw a
-        // NeedsRefreshException
-        if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
-            EntryUpdateState updateState = getUpdateState(key);
-
-            synchronized (updateState) {
-                if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
-                    // No one else is currently updating this entry - grab ownership
-                    updateState.startUpdate();
-
-                    if (cacheEntry.isNew()) {
-                        accessEventType = CacheMapAccessEventType.MISS;
-                    } else {
-                        accessEventType = CacheMapAccessEventType.STALE_HIT;
-                    }
-                } else if (updateState.isUpdating()) {
-                    // Another thread is already updating the cache. We block if this
-                    // is a new entry, or blocking mode is enabled. Either putInCache()
-                    // or cancelUpdate() can cause this thread to resume.
-                    if (cacheEntry.isNew() || blocking) {
-                        do {
-                            try {
-                                updateState.wait();
-                            } catch (InterruptedException e) {
-                            }
-                        } while (updateState.isUpdating());
-
-                        if (updateState.isCancelled()) {
-                            // The updating thread cancelled the update, let this one have a go.
-                            updateState.startUpdate();
-
-                            // We put the updateState object back into the updateStates map so
-                            // any remaining threads waiting on this cache entry will be notified
-                            // once this thread has done its thing (either updated the cache or
-                            // cancelled the update). Without this code they'll get left hanging...
-                            synchronized (updateStates) {
-                                updateStates.put(key, updateState);
-                            }
-
-                            if (cacheEntry.isNew()) {
-                                accessEventType = CacheMapAccessEventType.MISS;
-                            } else {
-                                accessEventType = CacheMapAccessEventType.STALE_HIT;
-                            }
-                        } else if (updateState.isComplete()) {
-                            reload = true;
-                        } else {
-                            log.error("Invalid update state for cache entry " + key);
-                        }
-                    }
-                } else {
-                    reload = true;
-                }
-            }
-        }
-
-        // If reload is true then another thread must have successfully rebuilt the cache entry
-        if (reload) {
-            cacheEntry = (CacheEntry) cacheMap.get(key);
-
-            if (cacheEntry != null) {
-                content = cacheEntry.getContent();
-            } else {
-                log.error("Could not reload cache entry after waiting for it to be rebuilt");
-            }
-        }
-
-        dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
-
-        // If we didn't end up getting a hit then we need to throw a NRE
-        if (accessEventType != CacheMapAccessEventType.HIT) {
-            throw new NeedsRefreshException(content);
-        }
-
-        return content;
-    }
+    public abstract Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException;
 
     /**
      * Set the listener to use for data persistence. Only one
      *
      * @param listener The implementation of a persistance listener
      */
-    public void setPersistenceListener(PersistenceListener listener) {
-        cacheMap.setPersistenceListener(listener);
-    }
+    public abstract void setPersistenceListener(PersistenceListener listener);
 
     /**
      * Retrieves the currently configured <code>PersistenceListener</code>.
      * @return the cache's <code>PersistenceListener</code>, or <code>null</code>
      * if no listener is configured.
      */
-    public PersistenceListener getPersistenceListener() {
-        return cacheMap.getPersistenceListener();
-    }
+    public abstract PersistenceListener getPersistenceListener();
 
     /**
      * Register a listener for Cache events. The listener must implement
      *
      * @param listener  The object that listens to events.
      */
-    public void addCacheEventListener(CacheEventListener listener, Class clazz) {
-        if (CacheEventListener.class.isAssignableFrom(clazz)) {
-            listenerList.add(clazz, listener);
-        } else {
-            log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener.");
-        }
-    }
+    public abstract void addCacheEventListener(CacheEventListener listener, Class clazz);
 
     /**
      * Cancels any pending update for this cache entry. This should <em>only</em>
      *
      * @param key The key for the cache entry in question.
      */
-    public void cancelUpdate(String key) {
-        EntryUpdateState state;
-
-        if (key != null) {
-            synchronized (updateStates) {
-                state = (EntryUpdateState) updateStates.get(key);
-
-                if (state != null) {
-                    synchronized (state) {
-                        state.cancelUpdate();
-                        state.notify();
-                    }
-                }
-            }
-        }
-    }
+    public abstract void cancelUpdate(String key);
 
     /**
      * Flush all entries in the cache on the given date/time.
      *
      * @param date The date at which all cache entries will be flushed.
      */
-    public void flushAll(Date date) {
-        flushAll(date, null);
-    }
+    public abstract void flushAll(Date date);
 
     /**
      * Flush all entries in the cache on the given date/time.
      * @param date The date at which all cache entries will be flushed.
      * @param origin The origin of this flush request (optional)
      */
-    public void flushAll(Date date, String origin) {
-        flushDateTime = date;
-
-        if (listenerList.getListenerCount() > 0) {
-            dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin);
-        }
-    }
+    public abstract void flushAll(Date date, String origin);
 
     /**
      * Flush the cache entry (if any) that corresponds to the cache key supplied.
      *
      * @param key The key of the entry to flush
      */
-    public void flushEntry(String key) {
-        flushEntry(key, null);
-    }
+    public abstract void flushEntry(String key);
 
     /**
      * Flush the cache entry (if any) that corresponds to the cache key supplied.
      * @param key The key of the entry to flush
      * @param origin The origin of this flush request (optional)
      */
-    public void flushEntry(String key, String origin) {
-        flushEntry(getCacheEntry(key, null, origin), origin);
-    }
+    public abstract void flushEntry(String key, String origin);
 
     /**
      * Flushes all objects that belong to the supplied group. On completion
      *
      * @param group The group to flush
      */
-    public void flushGroup(String group) {
-        flushGroup(group, null);
-    }
+    public abstract void flushGroup(String group);
 
     /**
      * Flushes all unexpired objects that belong to the supplied group. On
      * @param group The group to flush
      * @param origin The origin of this flush event (optional)
      */
-    public void flushGroup(String group, String origin) {
-        // Flush all objects in the group
-        Set groupEntries = cacheMap.getGroup(group);
-
-        if (groupEntries != null) {
-            Iterator itr = groupEntries.iterator();
-            String key;
-            CacheEntry entry;
-
-            while (itr.hasNext()) {
-                key = (String) itr.next();
-                entry = (CacheEntry) cacheMap.get(key);
-
-                if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
-                    flushEntry(entry, NESTED_EVENT);
-                }
-            }
-        }
-
-        if (listenerList.getListenerCount() > 0) {
-            dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
-        }
-    }
+    public abstract void flushGroup(String group, String origin);
 
     /**
      * Flush all entries with keys that match a given pattern
      * store cache entries in groups and use the {@link #flushGroup(String)} method
      * instead of relying on pattern flushing.
      */
-    public void flushPattern(String pattern) {
-        flushPattern(pattern, null);
-    }
+    public abstract void flushPattern(String pattern);
 
     /**
      * Flush all entries with keys that match a given pattern
      * store cache entries in groups and use the {@link #flushGroup(String, String)}
      * method instead of relying on pattern flushing.
      */
-    public void flushPattern(String pattern, String origin) {
-        // Check the pattern
-        if ((pattern != null) && (pattern.length() > 0)) {
-            String key = null;
-            CacheEntry entry = null;
-            Iterator itr = cacheMap.keySet().iterator();
-
-            while (itr.hasNext()) {
-                key = (String) itr.next();
-
-                if (key.indexOf(pattern) >= 0) {
-                    entry = (CacheEntry) cacheMap.get(key);
-
-                    if (entry != null) {
-                        flushEntry(entry, origin);
-                    }
-                }
-            }
-
-            if (listenerList.getListenerCount() > 0) {
-                dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
-            }
-        } else {
-            // Empty pattern, nothing to do
-        }
-    }
+    public abstract void flushPattern(String pattern, String origin);
 
     /**
      * Put an object in the cache specifying the key to use.
      * @param key       Key of the object in the cache.
      * @param content   The object to cache.
      */
-    public void putInCache(String key, Object content) {
-        putInCache(key, content, null, null, null);
-    }
+    public abstract void putInCache(String key, Object content);
 
     /**
      * Put an object in the cache specifying the key and refresh policy to use.
      * @param content   The object to cache.
      * @param policy   Object that implements refresh policy logic
      */
-    public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
-        putInCache(key, content, null, policy, null);
-    }
+    public abstract void putInCache(String key, Object content, EntryRefreshPolicy policy);
 
     /**
      * Put in object into the cache, specifying both the key to use and the
      * @param content   The object to cache
      * @param groups    The cache groups to add the object to
      */
-    public void putInCache(String key, Object content, String[] groups) {
-        putInCache(key, content, groups, null, null);
-    }
+    public abstract void putInCache(String key, Object content, String[] groups);
 
     /**
      * Put an object into the cache specifying both the key to use and the
      * @param content   The object to cache
      * @param policy    Object that implements the refresh policy logic
      */
-    public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin) {
-        CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin);
-        boolean isNewEntry = cacheEntry.isNew();
-
-        // [CACHE-118] If we have an existing entry, create a new CacheEntry so we can still access the old one later
-        if (!isNewEntry) {
-            cacheEntry = new CacheEntry(key, policy);
-        }
-
-        cacheEntry.setContent(content);
-        cacheEntry.setGroups(groups);
-        cacheMap.put(key, cacheEntry);
-
-        // Signal to any threads waiting on this update that it's now ready for them
-        // in the cache!
-        completeUpdate(key);
-
-        if (listenerList.getListenerCount() > 0) {
-            CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
-
-            if (isNewEntry) {
-                dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
-            } else {
-                dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
-            }
-        }
-    }
+    public abstract void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin);
 
     /**
      * Unregister a listener for Cache events.
      *
      * @param listener  The object that currently listens to events.
      */
-    public void removeCacheEventListener(CacheEventListener listener, Class clazz) {
-        listenerList.remove(clazz, listener);
-    }
+    public abstract void removeCacheEventListener(CacheEventListener listener, Class clazz);
 
     /**
-     * Get an entry from this cache or create one if it doesn't exist.
-     *
-     * @param key    The key of the cache entry
-     * @param policy Object that implements refresh policy logic
-     * @param origin The origin of request (optional)
-     * @return CacheEntry for the specified key.
-     */
-    protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) {
-        CacheEntry cacheEntry = null;
-
-        // Verify that the key is valid
-        if ((key == null) || (key.length() == 0)) {
-            throw new IllegalArgumentException("getCacheEntry called with an empty or null key");
-        }
-
-        cacheEntry = (CacheEntry) cacheMap.get(key);
-
-        // if the cache entry does not exist, create a new one
-        if (cacheEntry == null) {
-            if (log.isDebugEnabled()) {
-                log.debug("No cache entry exists for key='" + key + "', creating");
-            }
-
-            cacheEntry = new CacheEntry(key, policy);
-        }
-
-        return cacheEntry;
-    }
-
-    /**
-     * Indicates whether or not the cache entry is stale.
-     *
-     * @param cacheEntry     The cache entry to test the freshness of.
-     * @param refreshPeriod  The maximum allowable age of the entry, in seconds.
-     * @param cronExpiry     A cron expression specifying absolute date(s) and/or time(s)
-     * that the cache entry should expire at. If the cache entry was refreshed prior to
-     * the most recent match for the cron expression, the entry will be considered stale.
-     *
-     * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
-     */
-    protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
-        boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry);
-
-        if ((cronExpiry != null) && (cronExpiry.length() > 0)) {
-            try {
-                FastCronParser parser = new FastCronParser(cronExpiry);
-                result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate());
-            } catch (ParseException e) {
-                log.warn(e);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Get the updating cache entry from the update map. If one is not found,
-     * create a new one (with state {@link EntryUpdateState#NOT_YET_UPDATING})
-     * and add it to the map.
-     *
-     * @param key The cache key for this entry
-     *
-     * @return the CacheEntry that was found (or added to) the updatingEntries
-     * map.
-     */
-    protected EntryUpdateState getUpdateState(String key) {
-        EntryUpdateState updateState;
-
-        synchronized (updateStates) {
-            // Try to find the matching state object in the updating entry map.
-            updateState = (EntryUpdateState) updateStates.get(key);
-
-            if (updateState == null) {
-                // It's not there so add it.
-                updateState = new EntryUpdateState();
-                updateStates.put(key, updateState);
-            }
-        }
-
-        return updateState;
-    }
-
-    /**
-     * Completely clears the cache.
-     */
-    protected void clear() {
-        cacheMap.clear();
-    }
-
-    /**
-     * Removes the update state for the specified key and notifies any other
-     * threads that are waiting on this object. This is called automatically
-     * by the {@link #putInCache} method.
-     *
-     * @param key The cache key that is no longer being updated.
-     */
-    protected void completeUpdate(String key) {
-        EntryUpdateState state;
-
-        synchronized (updateStates) {
-            state = (EntryUpdateState) updateStates.remove(key);
-
-            if (state != null) {
-                synchronized (state) {
-                    if (!state.isUpdating()) {
-                        state.startUpdate();
-                    }
-
-                    state.completeUpdate();
-                    state.notifyAll();
-                }
-            }
-        }
-    }
+    * Completely clears the cache.
+    */
+    public abstract void clear();
 
     /**
      * Completely removes a cache entry from the cache and its associated cache
      *
      * @param key The key of the entry to remove.
      */
-    public void removeEntry(String key) {
-        removeEntry(key, null);
-    }
+    public abstract void removeEntry(String key);
 
-    /**
-     * Completely removes a cache entry from the cache and its associated cache
-     * groups.
-     *
-     * @param key    The key of the entry to remove.
-     * @param origin The origin of this remove request.
-     */
-    protected void removeEntry(String key, String origin) {
-        CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
-        cacheMap.remove(key);
-
-        if (listenerList.getListenerCount() > 0) {
-            CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
-            dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_REMOVED, event);
-        }
-    }
-
-    /**
-     * Dispatch a cache entry event to all registered listeners.
-     *
-     * @param eventType   The type of event (used to branch on the proper method)
-     * @param event       The event that was fired
-     */
-    private void dispatchCacheEntryEvent(CacheEntryEventType eventType, CacheEntryEvent event) {
-        // Guaranteed to return a non-null array
-        Object[] listeners = listenerList.getListenerList();
-
-        // Process the listeners last to first, notifying
-        // those that are interested in this event
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == CacheEntryEventListener.class) {
-                if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheEntryAdded(event);
-                } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheEntryUpdated(event);
-                } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheEntryFlushed(event);
-                } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheEntryRemoved(event);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dispatch a cache group event to all registered listeners.
-     *
-     * @param eventType The type of event (this is used to branch to the correct method handler)
-     * @param group     The cache group that the event applies to
-     * @param origin      The origin of this event (optional)
-     */
-    private void dispatchCacheGroupEvent(CacheEntryEventType eventType, String group, String origin) {
-        CacheGroupEvent event = new CacheGroupEvent(this, group, origin);
-
-        // Guaranteed to return a non-null array
-        Object[] listeners = listenerList.getListenerList();
-
-        // Process the listeners last to first, notifying
-        // those that are interested in this event
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == CacheEntryEventListener.class) {
-                if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheGroupFlushed(event);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dispatch a cache map access event to all registered listeners.
-     *
-     * @param eventType     The type of event
-     * @param entry         The entry that was affected.
-     * @param origin        The origin of this event (optional)
-     */
-    private void dispatchCacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) {
-        CacheMapAccessEvent event = new CacheMapAccessEvent(eventType, entry, origin);
-
-        // Guaranteed to return a non-null array
-        Object[] listeners = listenerList.getListenerList();
-
-        // Process the listeners last to first, notifying
-        // those that are interested in this event
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == CacheMapAccessEventListener.class) {
-                ((CacheMapAccessEventListener) listeners[i + 1]).accessed(event);
-            }
-        }
-    }
-
-    /**
-     * Dispatch a cache pattern event to all registered listeners.
-     *
-     * @param eventType The type of event (this is used to branch to the correct method handler)
-     * @param pattern     The cache pattern that the event applies to
-     * @param origin      The origin of this event (optional)
-     */
-    private void dispatchCachePatternEvent(CacheEntryEventType eventType, String pattern, String origin) {
-        CachePatternEvent event = new CachePatternEvent(this, pattern, origin);
-
-        // Guaranteed to return a non-null array
-        Object[] listeners = listenerList.getListenerList();
-
-        // Process the listeners last to first, notifying
-        // those that are interested in this event
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == CacheEntryEventListener.class) {
-                if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cachePatternFlushed(event);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dispatches a cache-wide event to all registered listeners.
-     *
-     * @param eventType The type of event (this is used to branch to the correct method handler)
-     * @param origin The origin of this event (optional)
-     */
-    private void dispatchCachewideEvent(CachewideEventType eventType, Date date, String origin) {
-        CachewideEvent event = new CachewideEvent(this, date, origin);
-
-        // Guaranteed to return a non-null array
-        Object[] listeners = listenerList.getListenerList();
-
-        // Process the listeners last to first, notifying
-        // those that are interested in this event
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == CacheEntryEventListener.class) {
-                if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
-                    ((CacheEntryEventListener) listeners[i + 1]).cacheFlushed(event);
-                }
-            }
-        }
-    }
-
-    /**
-     * Flush a cache entry. On completion of the flush, a
-     * <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
-     *
-     * @param entry The entry to flush
-     * @param origin The origin of this flush event (optional)
-     */
-    private void flushEntry(CacheEntry entry, String origin) {
-        String key = entry.getKey();
-
-        // Flush the object itself
-        entry.flush();
-
-        if (!entry.isNew()) {
-            // Update the entry's state in the map
-            cacheMap.put(key, entry);
-        }
-
-        // Trigger an ENTRY_FLUSHED event. [CACHE-107] Do this for all flushes.
-        if (listenerList.getListenerCount() > 0) {
-            CacheEntryEvent event = new CacheEntryEvent(this, entry, origin);
-            dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_FLUSHED, event);
-        }
-    }
+    public abstract EventListenerList getListenerList();
 }

File src/core/java/com/opensymphony/oscache/base/CacheEntry.java

  */
 public class CacheEntry implements Serializable {
     /**
-     * Default initialization value for the creation time and the last
-     * update time. This is a placeholder that indicates the value has
-     * not been set yet.
-     */
+ * Default initialization value for the creation time and the last
+ * update time. This is a placeholder that indicates the value has
+ * not been set yet.
+ */
     private static final byte NOT_YET = -1;
 
     /**
-     * Specifying this as the refresh period for the
-     * {@link #needsRefresh(int)} method will ensure
-     * an entry does not become stale until it is
-     * either explicitly flushed or a custom refresh
-     * policy causes the entry to expire.
-     */
+ * Specifying this as the refresh period for the
+ * {@link #needsRefresh(int)} method will ensure
+ * an entry does not become stale until it is
+ * either explicitly flushed or a custom refresh
+ * policy causes the entry to expire.
+ */
     public static final int INDEFINITE_EXPIRY = -1;
 
     /**
-     * The entry refresh policy object to use for this cache entry. This is optional.
-     */
+ * The entry refresh policy object to use for this cache entry. This is optional.
+ */
     private EntryRefreshPolicy policy = null;
+    private final EntryUpdateState updateState;
 
     /**
-     * The actual content that is being cached. Wherever possible this object
-     * should be serializable. This allows <code>PersistenceListener</code>s
-     * to serialize the cache entries to disk or database.
-     */
+ * The actual content that is being cached. Wherever possible this object
+ * should be serializable. This allows <code>PersistenceListener</code>s
+ * to serialize the cache entries to disk or database.
+ */
     private Object content = null;
 
     /**
-     * The set of cache groups that this cache entry belongs to, if any.
-     */
+ * The set of cache groups that this cache entry belongs to, if any.
+ */
     private Set groups = null;
 
     /**
-     *  The unique cache key for this entry
-     */
+ *  The unique cache key for this entry
+ */
     private String key;
 
     /**
-     * <code>true</code> if this entry was flushed
-     */
+ * <code>true</code> if this entry was flushed
+ */
     private boolean wasFlushed = false;
 
     /**
-     * The time this entry was created.
-     */
+ * The time this entry was created.
+ */
     private long created = NOT_YET;
 
     /**
-     * The time this emtry was last updated.
-     */
+ * The time this emtry was last updated.
+ */
     private long lastUpdate = NOT_YET;
 
     /**
-     * Construct a new CacheEntry using the key provided.
-     *
-     * @param key    The key of this CacheEntry
-     */
+ * Construct a new CacheEntry using the key provided.
+ *
+ * @param key    The key of this CacheEntry
+ */
     public CacheEntry(String key) {
         this(key, null);
     }
 
     /**
-     * Construct a CacheEntry.
-     *
-     * @param key      The unique key for this <code>CacheEntry</code>.
-     * @param policy   Object that implements refresh policy logic. This parameter
-     * is optional.
-     */
+ * Construct a CacheEntry.
+ *
+ * @param key      The unique key for this <code>CacheEntry</code>.
+ * @param policy   Object that implements refresh policy logic. This parameter
+ * is optional.
+ */
     public CacheEntry(String key, EntryRefreshPolicy policy) {
         this(key, policy, null);
     }
 
     /**
-     * Construct a CacheEntry.
-     *
-     * @param key     The unique key for this <code>CacheEntry</code>.
-     * @param policy  The object that implements the refresh policy logic. This
-     * parameter is optional.
-     * @param groups  The groups that this <code>CacheEntry</code> belongs to. This
-     * parameter is optional.
-     */
+ * Construct a CacheEntry.
+ *
+ * @param key     The unique key for this <code>CacheEntry</code>.
+ * @param policy  The object that implements the refresh policy logic. This
+ * parameter is optional.
+ * @param groups  The groups that this <code>CacheEntry</code> belongs to. This
+ * parameter is optional.
+ */
     public CacheEntry(String key, EntryRefreshPolicy policy, String[] groups) {
         this.key = key;
 
 
         this.policy = policy;
         this.created = System.currentTimeMillis();
+        this.updateState = new EntryUpdateState();
     }
 
     /**
-     * Sets the actual content that is being cached. Wherever possible this
-     * object should be <code>Serializable</code>, however it is not an
-     * absolute requirement when using a memory-only cache. Being <code>Serializable</code>
-     * allows <code>PersistenceListener</code>s to serialize the cache entries to disk
-     * or database.
-     *
-     * @param value The content to store in this CacheEntry.
-     */
+ * Sets the actual content that is being cached. Wherever possible this
+ * object should be <code>Serializable</code>, however it is not an
+ * absolute requirement when using a memory-only cache. Being <code>Serializable</code>
+ * allows <code>PersistenceListener</code>s to serialize the cache entries to disk
+ * or database.
+ *
+ * @param value The content to store in this CacheEntry.
+ */
     public synchronized void setContent(Object value) {
         content = value;
         lastUpdate = System.currentTimeMillis();
     }
 
     /**
-     * Get the cached content from this CacheEntry.
-     *
-     * @return The content of this CacheEntry.
-     */
+ * Get the cached content from this CacheEntry.
+ *
+ * @return The content of this CacheEntry.
+ */
     public Object getContent() {
         return content;
     }
 
     /**
-     * Get the date this CacheEntry was created.
-     *
-     * @return The date this CacheEntry was created.
-     */
+ * Get the date this CacheEntry was created.
+ *
+ * @return The date this CacheEntry was created.
+ */
     public long getCreated() {
         return created;
     }
 
     /**
-     * Sets the cache groups for this entry.
-     *
-     * @param groups A string array containing all the group names
-     */
+ * Sets the cache groups for this entry.
+ *
+ * @param groups A string array containing all the group names
+ */
     public synchronized void setGroups(String[] groups) {
         if (groups != null) {
             this.groups = new HashSet(groups.length);
     }
 
     /**
-     * Sets the cache groups for this entry
-     *
-     * @param groups A collection containing all the group names
-     */
+ * Sets the cache groups for this entry
+ *
+ * @param groups A collection containing all the group names
+ */
     public void setGroups(Collection groups) {
         if (groups != null) {
             this.groups = new HashSet(groups);
     }
 
     /**
-     * Gets the cache groups that this cache entry belongs to.
-     * These returned groups should be treated as immuatable.
-     *
-     * @return A set containing the names of all the groups that
-     * this cache entry belongs to.
-     */
+ * Gets the cache groups that this cache entry belongs to.
+ * These returned groups should be treated as immuatable.
+ *
+ * @return A set containing the names of all the groups that
+ * this cache entry belongs to.
+ */
     public Set getGroups() {
         return groups;
     }
 
     /**
-     * Get the key of this CacheEntry
-     *
-     * @return The key of this CacheEntry
-     */
+ * Get the key of this CacheEntry
+ *
+ * @return The key of this CacheEntry
+ */
     public String getKey() {
         return key;
     }
 
     /**
-     * Set the date this CacheEntry was last updated.
-     *
-     * @param update The time (in milliseconds) this CacheEntry was last updated.
-     */
+ * Set the date this CacheEntry was last updated.
+ *
+ * @param update The time (in milliseconds) this CacheEntry was last updated.
+ */
     public void setLastUpdate(long update) {
         lastUpdate = update;
     }
 
     /**
-     * Get the date this CacheEntry was last updated.
-     *
-     * @return The date this CacheEntry was last updated.
-     */
+ * Get the date this CacheEntry was last updated.
+ *
+ * @return The date this CacheEntry was last updated.
+ */
     public long getLastUpdate() {
         return lastUpdate;
     }
 
     /**
-     * Indicates whether this CacheEntry is a freshly created one and
-     * has not yet been assigned content or placed in a cache.
-     *
-     * @return <code>true</code> if this entry is newly created
-     */
+ * Indicates whether this CacheEntry is a freshly created one and
+ * has not yet been assigned content or placed in a cache.
+ *
+ * @return <code>true</code> if this entry is newly created
+ */
     public boolean isNew() {
         return lastUpdate == NOT_YET;
     }
 
     /**
-     * Get the size of the cache entry in bytes (roughly).<p>
-     *
-     * Currently this method only handles <code>String<code>s and
-     * {@link ResponseContent} objects.
-     *
-     * @return The approximate size of the entry in bytes, or -1 if the
-     * size could not be estimated.
-     */
+ * Get the size of the cache entry in bytes (roughly).<p>
+ *
+ * Currently this method only handles <code>String<code>s and
+ * {@link ResponseContent} objects.
+ *
+ * @return The approximate size of the entry in bytes, or -1 if the
+ * size could not be estimated.
+ */
     public int getSize() {
         // a char is two bytes
         int size = (key.length() * 2) + 4;
     }
 
     /**
-     * Flush the entry from cache.
-     * note that flushing the cache doesn't actually remove the cache contents
-     * it just tells the CacheEntry that it needs a refresh next time it is asked
-     * this is so that the content is still there for a <usecached />.
-     */
+ * Flush the entry from cache.
+ * note that flushing the cache doesn't actually remove the cache contents
+ * it just tells the CacheEntry that it needs a refresh next time it is asked
+ * this is so that the content is still there for a <usecached />.
+ */
     public void flush() {
         wasFlushed = true;
     }
 
     /**
-     * Check if this CacheEntry needs to be refreshed.
-     *
-     * @param refreshPeriod The period of refresh (in seconds). Passing in
-     * {@link #INDEFINITE_EXPIRY} will result in the content never becoming
-     * stale unless it is explicitly flushed, or expired by a custom
-     * {@link EntryRefreshPolicy}. Passing in 0 will always result in a
-     * refresh being required.
-     *
-     * @return Whether or not this CacheEntry needs refreshing.
-     */
+ * Check if this CacheEntry needs to be refreshed.
+ *
+ * @param refreshPeriod The period of refresh (in seconds). Passing in
+ * {@link #INDEFINITE_EXPIRY} will result in the content never becoming
+ * stale unless it is explicitly flushed, or expired by a custom
+ * {@link EntryRefreshPolicy}. Passing in 0 will always result in a
+ * refresh being required.
+ *
+ * @return Whether or not this CacheEntry needs refreshing.
+ */
     public boolean needsRefresh(int refreshPeriod) {
         boolean needsRefresh;
 
 
         return needsRefresh;
     }
+
+    public EntryUpdateState getUpdateState() {
+        return updateState;
+    }
 }

File src/core/java/com/opensymphony/oscache/base/CacheImpl.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.base.events.CacheEntryEvent;
+import com.opensymphony.oscache.base.events.CacheEntryEventListener;
+import com.opensymphony.oscache.base.events.CacheEntryEventType;
+import com.opensymphony.oscache.base.events.CacheEventListener;
+import com.opensymphony.oscache.base.events.CacheGroupEvent;
+import com.opensymphony.oscache.base.events.CacheMapAccessEvent;
+import com.opensymphony.oscache.base.events.CacheMapAccessEventListener;
+import com.opensymphony.oscache.base.events.CacheMapAccessEventType;
+import com.opensymphony.oscache.base.events.CachePatternEvent;
+import com.opensymphony.oscache.base.events.CachewideEvent;
+import com.opensymphony.oscache.base.events.CachewideEventType;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+import com.opensymphony.oscache.util.FastCronParser;
+
+import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
+import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+import java.text.ParseException;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.event.EventListenerList;
+
+/**
+ * Provides an interface to the cache itself. Creating an instance of this class
+ * will create a cache that behaves according to its construction parameters.
+ * The public API provides methods to manage objects in the cache and configure
+ * any cache event listeners.
+ *
+ * @version        $Revision$
+ * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
+ */
+public class CacheImpl implements Serializable, Cache {
+    private static transient final Log log = LogFactory.getLog(Cache.class);
+
+    /**
+     * A list of all registered event listeners for this cache.
+     */
+    protected EventListenerList listenerList = new EventListenerList();
+
+    /**
+     * Date of last complete cache flush.
+     */
+    private Date flushDateTime = null;
+
+    /**
+     * The actual cache map. This is where the cached objects are held.
+     */
+    private Map cacheMap = null;
+
+    /**
+    * The groups map. This is where the cached objects group memberships are stored.
+    */
+    private Map groups = null;
+    private PersistenceListener persistenceListener;
+
+    /**
+     * Indicates whether the cache blocks requests until new content has
+     * been generated or just serves stale content instead.
+     */
+    private boolean blocking = false;
+    private boolean overflowPersistence;
+    private boolean unlimitedDiskCache;
+    private boolean useMemoryCaching;
+    private int capacity;
+
+    /**
+     * Create a new Cache
+     *
+     * @param useMemoryCaching Specify if the memory caching is going to be used
+     * @param unlimitedDiskCache Specify if the disk caching is unlimited
+     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
+     */
+    public CacheImpl(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence) {
+        this(useMemoryCaching, unlimitedDiskCache, overflowPersistence, false, null, 0);
+    }
+
+    /**
+     * Create a new Cache.
+     *
+     * If a valid algorithm class is specified, it will be used for this cache.
+     * Otherwise if a capacity is specified, it will use LRUCache.
+     * If no algorithm or capacity is specified UnlimitedCache is used.
+     *
+     * @see com.opensymphony.oscache.base.algorithm.LRUCache
+     * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
+     * @param useMemoryCaching Specify if the memory caching is going to be used
+     * @param unlimitedDiskCache Specify if the disk caching is unlimited
+     * @param overflowPersistence Specify if the persistent cache is used in overflow only mode
+     * @param blocking This parameter takes effect when a cache entry has
+     * just expired and several simultaneous requests try to retrieve it. While
+     * one request is rebuilding the content, the other requests will either
+     * block and wait for the new content (<code>blocking == true</code>) or
+     * instead receive a copy of the stale content so they don't have to wait
+     * (<code>blocking == false</code>). the default is <code>false</code>,
+     * which provides better performance but at the expense of slightly stale
+     * data being served.
+     * @param algorithmClass The class implementing the desired algorithm
+     * @param capacity The capacity
+     */
+    public CacheImpl(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence, boolean blocking, String algorithmClass, int capacity) {
+        try {
+            cacheMap = new ConcurrentHashMap();
+        } catch (Exception e) {
+            log.error("Invalid class name for cache algorithm class. " + e.toString());
+        }
+
+        this.unlimitedDiskCache = unlimitedDiskCache;
+        this.overflowPersistence = overflowPersistence;
+        this.useMemoryCaching = useMemoryCaching;
+
+        this.blocking = blocking;
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#setCapacity(int)
+         */
+    public void setCapacity(int capacity) {
+        this.capacity = capacity;
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#isFlushed(com.opensymphony.oscache.base.CacheEntry)
+         */
+    public boolean isFlushed(CacheEntry cacheEntry) {
+        if (flushDateTime != null) {
+            long lastUpdate = cacheEntry.getLastUpdate();
+
+            return (flushDateTime.getTime() >= lastUpdate);
+        } else {
+            return false;
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#getFromCache(java.lang.String)
+         */
+    public Object getFromCache(String key) throws NeedsRefreshException {
+        return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#getFromCache(java.lang.String, int)
+         */
+    public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
+        return getFromCache(key, refreshPeriod, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#getFromCache(java.lang.String, int, java.lang.String)
+         */
+    public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
+        CacheEntry cacheEntry = getCacheEntry(key, null, null);
+
+        Object content = cacheEntry.getContent();
+        CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
+
+        boolean reload = false;
+
+        // Check if this entry has expired or has not yet been added to the cache. If
+        // so, we need to decide whether to block, serve stale content or throw a
+        // NeedsRefreshException
+        if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
+			
+			
+            EntryUpdateState updateState = cacheEntry.getUpdateState();
+            synchronized (updateState) {
+                if (updateState.isAwaitingUpdate() || updateState.isCancelled() || updateState.isComplete()) {
+                    // No one else is currently updating this entry - grab ownership
+                    updateState.startUpdate();
+
+                    if (cacheEntry.isNew()) {
+                        accessEventType = CacheMapAccessEventType.MISS;
+                    } else {
+                        accessEventType = CacheMapAccessEventType.STALE_HIT;
+                    }
+                } else if (updateState.isUpdating()) {
+                    // Another thread is already updating the cache. We block if this
+                    // is a new entry, or blocking mode is enabled. Either putInCache()
+                    // or cancelUpdate() can cause this thread to resume.
+                    if (cacheEntry.isNew() || blocking) {
+                        do {
+                            try {
+                                updateState.wait();
+                            } catch (InterruptedException e) {
+                            }
+                        } while (updateState.isUpdating());
+
+                        if (updateState.isCancelled()) {
+                            // The updating thread cancelled the update, let this one have a go.
+                            updateState.startUpdate();
+
+                            if (cacheEntry.isNew()) {
+                                accessEventType = CacheMapAccessEventType.MISS;
+                            } else {
+                                accessEventType = CacheMapAccessEventType.STALE_HIT;
+                            }
+                        } else if (updateState.isComplete()) {
+                            reload = true;
+                        } else {
+                            log.error("Invalid update state for cache entry " + key);
+                        }
+                    }
+                } else {
+                    reload = true;
+                }
+            }
+        }
+
+        // If reload is true then another thread must have successfully rebuilt the cache entry
+        if (reload) {
+            cacheEntry = (CacheEntry) cacheMap.get(key);
+
+            if (cacheEntry != null) {
+                content = cacheEntry.getContent();
+            } else {
+                log.error("Could not reload cache entry after waiting for it to be rebuilt");
+            }
+        }
+
+        dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
+
+        // If we didn't end up getting a hit then we need to throw a NRE
+        if (accessEventType != CacheMapAccessEventType.HIT) {
+            throw new NeedsRefreshException(content);
+        }
+
+        return content;
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#setPersistenceListener(com.opensymphony.oscache.base.persistence.PersistenceListener)
+         */
+    public void setPersistenceListener(PersistenceListener listener) {
+        this.persistenceListener = listener;
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#getPersistenceListener()
+         */
+    public PersistenceListener getPersistenceListener() {
+        return this.persistenceListener;
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#addCacheEventListener(com.opensymphony.oscache.base.events.CacheEventListener, java.lang.Class)
+         */
+    public void addCacheEventListener(CacheEventListener listener, Class clazz) {
+        if (CacheEventListener.class.isAssignableFrom(clazz)) {
+            listenerList.add(clazz, listener);
+        } else {
+            log.error("The class '" + clazz.getName() + "' is not a CacheEventListener. Ignoring this listener.");
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#cancelUpdate(java.lang.String)
+         */
+    public void cancelUpdate(String key) {
+        EntryUpdateState state;
+
+        if (key != null) {
+            CacheEntry cacheEntry = (CacheEntry) cacheMap.get(key);
+            state = (EntryUpdateState) cacheEntry.getUpdateState();
+
+            if (state != null) {
+                synchronized (state) {
+                    state.cancelUpdate();
+                    state.notify();
+                }
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushAll(java.util.Date)
+         */
+    public void flushAll(Date date) {
+        flushAll(date, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushAll(java.util.Date, java.lang.String)
+         */
+    public void flushAll(Date date, String origin) {
+        flushDateTime = date;
+
+        if (listenerList.getListenerCount() > 0) {
+            dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, origin);
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushEntry(java.lang.String)
+         */
+    public void flushEntry(String key) {
+        flushEntry(key, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushEntry(java.lang.String, java.lang.String)
+         */
+    public void flushEntry(String key, String origin) {
+        flushEntry(getCacheEntry(key, null, origin), origin);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushGroup(java.lang.String)
+         */
+    public void flushGroup(String group) {
+        flushGroup(group, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushGroup(java.lang.String, java.lang.String)
+         */
+    public void flushGroup(String group, String origin) {
+        // Flush all objects in the group
+        Set groupEntries = (Set) groups.get(group);
+
+        if (groupEntries != null) {
+            Iterator itr = groupEntries.iterator();
+            String key;
+            CacheEntry entry;
+
+            while (itr.hasNext()) {
+                key = (String) itr.next();
+                entry = (CacheEntry) cacheMap.get(key);
+
+                if ((entry != null) && !entry.needsRefresh(CacheEntry.INDEFINITE_EXPIRY)) {
+                    flushEntry(entry, NESTED_EVENT);
+                }
+            }
+        }
+
+        if (listenerList.getListenerCount() > 0) {
+            dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushPattern(java.lang.String)
+         */
+    public void flushPattern(String pattern) {
+        flushPattern(pattern, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#flushPattern(java.lang.String, java.lang.String)
+         */
+    public void flushPattern(String pattern, String origin) {
+        // Check the pattern
+        if ((pattern != null) && (pattern.length() > 0)) {
+            String key = null;
+            CacheEntry entry = null;
+            Iterator itr = cacheMap.keySet().iterator();
+
+            while (itr.hasNext()) {
+                key = (String) itr.next();
+
+                if (key.indexOf(pattern) >= 0) {
+                    entry = (CacheEntry) cacheMap.get(key);
+
+                    if (entry != null) {
+                        flushEntry(entry, origin);
+                    }
+                }
+            }
+
+            if (listenerList.getListenerCount() > 0) {
+                dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
+            }
+        } else {
+            // Empty pattern, nothing to do
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#putInCache(java.lang.String, java.lang.Object)
+         */
+    public void putInCache(String key, Object content) {
+        putInCache(key, content, null, null, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#putInCache(java.lang.String, java.lang.Object, com.opensymphony.oscache.base.EntryRefreshPolicy)
+         */
+    public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
+        putInCache(key, content, null, policy, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#putInCache(java.lang.String, java.lang.Object, java.lang.String[])
+         */
+    public void putInCache(String key, Object content, String[] groups) {
+        putInCache(key, content, groups, null, null);
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#putInCache(java.lang.String, java.lang.Object, java.lang.String[], com.opensymphony.oscache.base.EntryRefreshPolicy, java.lang.String)
+         */
+    public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin) {
+        CacheEntry cacheEntry = getCacheEntry(key, policy, origin);
+        boolean isNewEntry = cacheEntry.isNew();
+
+        synchronized (cacheEntry) {
+            cacheEntry.setContent(content);
+            cacheEntry.setGroups(groups);
+            cacheMap.put(key, cacheEntry);
+
+            if (groups != null) {
+                addGroupMappings(key, groups);
+            }
+        }
+
+        // Signal to any threads waiting on this update that it's now ready for them
+        // in the cache!
+        completeUpdate(cacheEntry);
+
+        if (listenerList.getListenerCount() > 0) {
+            CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
+
+            if (isNewEntry) {
+                dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
+            } else {
+                dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
+            }
+        }
+    }
+
+    /**
+    * Add this cache key to the groups specified groups.
+    * We have to treat the
+    * memory and disk group mappings seperately so they remain valid for their
+    * corresponding memory/disk caches. (eg if mem is limited to 100 entries
+    * and disk is unlimited, the group mappings will be different).
+    *
+    * @param key The cache key that we are ading to the groups.
+    * @param newGroups the set of groups we want to add this cache entry to.
+    * @param persist A flag to indicate whether the keys should be added to
+    * the persistent cache layer.
+    */
+    private void addGroupMappings(String key, String[] newGroups) {
+        // Add this CacheEntry to the groups that it is now a member of
+        for (int i = 0; i < newGroups.length; i++) {
+            String groupName = newGroups[i];
+
+            if (groups == null) {
+                groups = new ConcurrentHashMap();
+            }
+
+            Set group = (Set) groups.get(groupName);
+
+            if (group == null) {
+                group = new CopyOnWriteArraySet();
+                groups.put(groupName, group);
+            }
+
+            group.add(key);
+        }
+    }
+
+    /* (non-Javadoc)
+         * @see com.opensymphony.oscache.base.CacheAPI#removeCacheEventListener(com.opensymphony.oscache.base.events.CacheEventListener, java.lang.Class)
+         */
+    public void removeCacheEventListener(CacheEventListener listener, Class clazz) {
+        listenerList.remove(clazz, listener);
+    }
+
+    /**
+     * Get an entry from this cache or create one if it doesn't exist.
+     *
+     * @param key    The key of the cache entry
+     * @param policy Object that implements refresh policy logic
+     * @param origin The origin of request (optional)
+     * @return CacheEntry for the specified key.
+     */
+    protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) {
+        CacheEntry cacheEntry = null;
+
+        // Verify that the key is valid
+        if ((key == null) || (key.length() == 0)) {
+            throw new IllegalArgumentException("getCacheEntry called with an empty or null key");
+        }
+
+        cacheEntry = (CacheEntry) cacheMap.get(key);
+
+        // if the cache entry does not exist, create a new one
+        if (cacheEntry == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("No cache entry exists for key='" + key + "', creating");
+            }