Commits

Anonymous committed 2edf2a3

no message

Comments (0)

Files changed (80)

src/core/etc/jalopy.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<jalopy>
+    <general>
+        <compliance>
+            <version>14</version>
+        </compliance>
+        <style>
+            <description>OpenSymphony</description>
+            <name>OpenSymphony</name>
+        </style>
+    </general>
+    <inspector>
+        <enable>false</enable>
+        <naming>
+            <classes>
+                <abstract>[A-Z][a-zA-Z0-9]+</abstract>
+                <general>[A-Z][a-zA-Z0-9]+</general>
+            </classes>
+            <fields>
+                <default>[a-z][\w]+</default>
+                <defaultStatic>[a-z][\w]+</defaultStatic>
+                <defaultStaticFinal>[a-zA-Z][\w]+</defaultStaticFinal>
+                <private>[a-z][\w]+</private>
+                <privateStatic>[a-z][\w]+</privateStatic>
+                <privateStaticFinal>[a-zA-Z][\w]+</privateStaticFinal>
+                <protected>[a-z][\w]+</protected>
+                <protectedStatic>[a-z][\w]+</protectedStatic>
+                <protectedStaticFinal>[a-zA-Z][\w]+</protectedStaticFinal>
+                <public>[a-z][\w]+</public>
+                <publicStatic>[a-z][\w]+</publicStatic>
+                <publicStaticFinal>[a-zA-Z][\w]+</publicStaticFinal>
+            </fields>
+            <interfaces>[A-Z][a-zA-Z0-9]+</interfaces>
+            <labels>\w+</labels>
+            <methods>
+                <default>[a-z][\w]+</default>
+                <defaultStatic>[a-z][\w]+</defaultStatic>
+                <defaultStaticFinal>[a-z][\w]+</defaultStaticFinal>
+                <private>[a-z][\w]+</private>
+                <privateStatic>[a-z][\w]+</privateStatic>
+                <privateStaticFinal>[a-z][\w]+</privateStaticFinal>
+                <protected>[a-z][\w]+</protected>
+                <protectedStatic>[a-z][\w]+</protectedStatic>
+                <protectedStaticFinal>[a-z][\w]+</protectedStaticFinal>
+                <public>[a-z][\w]+</public>
+                <publicStatic>[a-z][\w]+</publicStatic>
+                <publicStaticFinal>[a-z][\w]+</publicStaticFinal>
+            </methods>
+            <packages>[a-z]+(?:\.[a-z]+)*</packages>
+            <parameters>
+                <default>[a-z][\w]+</default>
+                <final>[a-z][\w]+</final>
+            </parameters>
+            <variables>[a-z][\w]*</variables>
+        </naming>
+        <tips>
+            <adhereToNamingConvention>false</adhereToNamingConvention>
+            <alwaysOverrideEquals>false</alwaysOverrideEquals>
+            <alwaysOverrideHashCode>false</alwaysOverrideHashCode>
+            <avoidThreadGroups>false</avoidThreadGroups>
+            <declareCollectionComment>false</declareCollectionComment>
+            <dontIgnoreExceptions>false</dontIgnoreExceptions>
+            <dontSubstituteObjectEquals>false</dontSubstituteObjectEquals>
+            <neverDeclareException>false</neverDeclareException>
+            <neverDeclareThrowable>false</neverDeclareThrowable>
+            <neverInvokeWaitOutsideLoop>false</neverInvokeWaitOutsideLoop>
+            <neverReturnZeroArrays>false</neverReturnZeroArrays>
+            <neverUseEmptyFinally>false</neverUseEmptyFinally>
+            <obeyContractEquals>false</obeyContractEquals>
+            <overrideToString>false</overrideToString>
+            <referToObjectsByInterface>false</referToObjectsByInterface>
+            <replaceStructureWithClass>false</replaceStructureWithClass>
+            <stringLiterallI18n>false</stringLiterallI18n>
+            <useInterfaceOnlyForTypes>false</useInterfaceOnlyForTypes>
+            <wrongCollectionComment>false</wrongCollectionComment>
+        </tips>
+    </inspector>
+    <internal>
+        <version>6</version>
+    </internal>
+    <messages>
+        <priority>
+            <general>30000</general>
+            <parser>30000</parser>
+            <parserJavadoc>30000</parserJavadoc>
+            <printer>30000</printer>
+            <printerJavadoc>30000</printerJavadoc>
+            <transform>30000</transform>
+        </priority>
+        <showErrorStackTrace>true</showErrorStackTrace>
+    </messages>
+    <misc>
+        <threadCount>1</threadCount>
+    </misc>
+    <printer>
+        <alignment>
+            <methodCallChain>true</methodCallChain>
+            <parameterMethodDeclaration>false</parameterMethodDeclaration>
+            <ternaryOperator>true</ternaryOperator>
+            <variableAssignment>false</variableAssignment>
+            <variableIdentifier>false</variableIdentifier>
+        </alignment>
+        <backup>
+            <directory>bak</directory>
+            <level>0</level>
+        </backup>
+        <blanklines>
+            <after>
+                <block>1</block>
+                <braceLeft>0</braceLeft>
+                <class>1</class>
+                <declaration>0</declaration>
+                <footer>1</footer>
+                <header>0</header>
+                <interface>1</interface>
+                <lastImport>1</lastImport>
+                <method>1</method>
+                <package>1</package>
+            </after>
+            <before>
+                <block>1</block>
+                <braceRight>0</braceRight>
+                <caseBlock>0</caseBlock>
+                <comment>
+                    <javadoc>1</javadoc>
+                    <multiline>1</multiline>
+                    <singleline>1</singleline>
+                </comment>
+                <controlStatement>0</controlStatement>
+                <declaration>1</declaration>
+                <footer>0</footer>
+                <header>0</header>
+            </before>
+            <keepUpTo>1</keepUpTo>
+        </blanklines>
+        <braces>
+            <empty>
+                <cuddle>false</cuddle>
+                <insertStatement>false</insertStatement>
+            </empty>
+            <insert>
+                <dowhile>true</dowhile>
+                <for>true</for>
+                <ifelse>true</ifelse>
+                <while>true</while>
+            </insert>
+            <remove>
+                <block>true</block>
+                <dowhile>false</dowhile>
+                <for>false</for>
+                <ifelse>false</ifelse>
+                <while>false</while>
+            </remove>
+            <treatDifferent>
+                <methodClass>false</methodClass>
+                <methodClassIfWrapped>false</methodClassIfWrapped>
+            </treatDifferent>
+        </braces>
+        <chunks>
+            <blanklines>true</blanklines>
+            <comments>true</comments>
+        </chunks>
+        <comments>
+            <format>
+                <multiline>false</multiline>
+            </format>
+            <javadoc>
+                <check>
+                    <innerclass>false</innerclass>
+                    <tags>false</tags>
+                    <throwsTags>false</throwsTags>
+                </check>
+                <fieldsShort>true</fieldsShort>
+                <generate>
+                    <class>1</class>
+                    <constructor>0</constructor>
+                    <field>0</field>
+                    <method>0</method>
+                </generate>
+                <parseComments>false</parseComments>
+                <tags>
+                    <in-line />
+                    <standard />
+                </tags>
+                <templates>
+                    <method>
+                        <bottom> */</bottom>
+                        <exception> * @throws $exceptionType$ DOCUMENT ME!</exception>
+                        <param> * @param $paramType$ DOCUMENT ME!</param>
+                        <return> * @return DOCUMENT ME!</return>
+                        <top>/**| * DOCUMENT ME!</top>
+                    </method>
+                </templates>
+            </javadoc>
+            <remove>
+                <javadoc>false</javadoc>
+                <multiline>false</multiline>
+                <singleline>false</singleline>
+            </remove>
+            <separator>
+                <fillCharacter>/</fillCharacter>
+                <insert>false</insert>
+                <insertRecursive>false</insertRecursive>
+                <text>
+                    <class>Inner Classes</class>
+                    <constructor>Constructors</constructor>
+                    <field>Instance fields</field>
+                    <initializer>Instance initializers</initializer>
+                    <interface>Inner Interfaces</interface>
+                    <method>Methods</method>
+                    <static>Static fields/initializers</static>
+                </text>
+            </separator>
+        </comments>
+        <environment />
+        <footer>
+            <keys />
+            <smartMode>0</smartMode>
+            <use>false</use>
+        </footer>
+        <header>
+            <keys>To change template for new class use|OpenSymphon</keys>
+            <smartMode>0</smartMode>
+            <text>/*| * Copyright (c) 2002-2003 by OpenSymphony| * All rights reserved.| */</text>
+            <use>true</use>
+        </header>
+        <history>
+            <policy>disabled</policy>
+        </history>
+        <imports>
+            <grouping>
+                <defaultDepth>3</defaultDepth>
+                <packages>*:0|gnu:2|java:2|javax:2</packages>
+            </grouping>
+            <policy>disabled</policy>
+            <sort>true</sort>
+        </imports>
+        <indentation>
+            <caseFromSwitch>true</caseFromSwitch>
+            <continuation>
+                <block>true</block>
+                <operator>false</operator>
+            </continuation>
+            <firstColumnComments>true</firstColumnComments>
+            <label>false</label>
+            <policy>
+                <deep>false</deep>
+            </policy>
+            <sizes>
+                <braceCuddled>1</braceCuddled>
+                <braceLeft>1</braceLeft>
+                <braceRight>0</braceRight>
+                <braceRightAfter>1</braceRightAfter>
+                <continuation>4</continuation>
+                <deep>55</deep>
+                <extends>-1</extends>
+                <general>4</general>
+                <implements>-1</implements>
+                <leading>0</leading>
+                <tabs>8</tabs>
+                <throws>-1</throws>
+                <trailingComment>1</trailingComment>
+            </sizes>
+            <tabs>
+                <enable>false</enable>
+                <onlyLeading>false</onlyLeading>
+            </tabs>
+        </indentation>
+        <misc>
+            <arrayBracketsAfterIdent>false</arrayBracketsAfterIdent>
+            <forceFormatting>false</forceFormatting>
+            <insertExpressionParentheses>true</insertExpressionParentheses>
+            <insertLoggingConditional>true</insertLoggingConditional>
+            <insertTrailingNewline>true</insertTrailingNewline>
+            <insertUID>false</insertUID>
+        </misc>
+        <sorting>
+            <declaration>
+                <class>true</class>
+                <constructor>true</constructor>
+                <enable>true</enable>
+                <interface>true</interface>
+                <method>false</method>
+                <order>static|field|initializer|constructor|method|interface|class</order>
+                <variable>true</variable>
+            </declaration>
+            <modifier>
+                <enable>false</enable>
+                <order>public|protected|private|abstract|static|final|synchronized|transient|volatile|native|strictfp</order>
+            </modifier>
+        </sorting>
+        <whitespace>
+            <after>
+                <comma>true</comma>
+                <semicolon>true</semicolon>
+                <typeCast>true</typeCast>
+            </after>
+            <before>
+                <braces>true</braces>
+                <brackets>false</brackets>
+                <bracketsTypes>false</bracketsTypes>
+                <caseColon>false</caseColon>
+                <operator>
+                    <not>false</not>
+                </operator>
+                <parentheses>
+                    <methodCall>false</methodCall>
+                    <methodDeclaration>false</methodDeclaration>
+                    <statement>true</statement>
+                </parentheses>
+            </before>
+            <padding>
+                <braces>false</braces>
+                <brackets>false</brackets>
+                <operator>
+                    <assignment>true</assignment>
+                    <bitwise>true</bitwise>
+                    <logical>true</logical>
+                    <mathematical>true</mathematical>
+                    <relational>true</relational>
+                    <shift>true</shift>
+                </operator>
+                <parenthesis>false</parenthesis>
+                <typeCast>false</typeCast>
+            </padding>
+        </whitespace>
+        <wrapping>
+            <always>
+                <after>
+                    <arrayElement>0</arrayElement>
+                    <braceRight>false</braceRight>
+                    <extendsTypes>false</extendsTypes>
+                    <implementsTypes>false</implementsTypes>
+                    <label>true</label>
+                    <methodCallChained>false</methodCallChained>
+                    <ternaryOperator>
+                        <first>false</first>
+                        <second>false</second>
+                    </ternaryOperator>
+                    <throwsTypes>false</throwsTypes>
+                </after>
+                <before>
+                    <braceLeft>false</braceLeft>
+                    <extends>false</extends>
+                    <implements>false</implements>
+                    <throws>false</throws>
+                </before>
+                <parameter>
+                    <methodCall>false</methodCall>
+                    <methodCallNested>false</methodCallNested>
+                    <methodDeclaration>false</methodDeclaration>
+                </parameter>
+            </always>
+            <general>
+                <beforeOperator>false</beforeOperator>
+                <enable>false</enable>
+                <lineLength>80</lineLength>
+            </general>
+            <ondemand>
+                <after>
+                    <assignment>false</assignment>
+                    <leftParenthesis>false</leftParenthesis>
+                    <parameter>false</parameter>
+                    <types>
+                        <extends>false</extends>
+                        <implements>false</implements>
+                        <throws>false</throws>
+                    </types>
+                </after>
+                <before>
+                    <rightParenthesis>false</rightParenthesis>
+                </before>
+                <groupingParentheses>false</groupingParentheses>
+            </ondemand>
+        </wrapping>
+    </printer>
+</jalopy>
+

src/core/etc/oscache.properties

+# CACHE DIRECTORY
+#
+# This is the directory on disk where caches will be stored.
+# it will be created if it doesn't already exist, but OSache
+# must be able to write to here.
+#
+# If you want to disable file caching, just comment out or remove this line.
+# Note: for Windows machines, this needs \ to be escaped
+# ie Windows:
+# cache.path=c:\\myapp\\cache
+# or *ix:
+# cache.path=/opt/myapp/cache
+
+
+# CACHE IN MEMORY
+#
+# If you want to disable memory caching, just comment out or remove this line.
+# Note: disabling memory AND disk caching is possible but fairly stupid ;)
+#
+# cache.memory=false
+
+
+# CACHE KEY
+#
+# This is the key that will be used to store the cache in the application
+# and session scope.
+#
+# If you want to set the cache key to anything other than the default
+# uncomment this line and change the cache.key
+#
+# cache.key=__oscache_cache
+
+
+# USE HOST DOMAIN NAME IN KEY
+#
+# Servers for multiple host domains may wish to add host name info to
+# the generation of the key.  If this is true, then uncomment the
+# following line.
+#
+# cache.use.host.domain.in.key=true
+
+
+# CACHE LISTENERS
+#
+# cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.BroadcastingCacheEventListener,  \
+#                       com.opensymphony.oscache.extra.CacheEntryEventListenerImpl,                      \
+#                       com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl,                  \
+#                       com.opensymphony.oscache.extra.ScopeEventListenerImpl
+
+
+# CACHE PERSISTENCE CLASS
+#
+# Specify the class to use for persistence.
+#
+# cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+
+
+# CACHE ALGORITHM
+#
+# Default cache algorithm to use. Note that in order to use an algorithm
+# the cache size must also be specified. If the cache size is not specified,
+# the cache algorithm will be Unlimited cache.
+#
+# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
+
+
+# CACHE SIZE
+#
+# Default cache size in number of item. If a size is specified but not
+# an algorithm, the cache algorithm used will be LRUCache.
+#
+cache.capacity=1000
+
+
+# CACHE UNLIMITED DISK
+# Use unlimited disk cache or not
+# cache.unlimited.disk=false
+
+
+# CLUSTER PROPERTIES
+#
+# Configuration properites for the JavaGroups clustering. Only one of these
+# should be specified. Default values (as shown below) will be used if niether
+# property is set. See the JavaGroups project (www.javagroups.com) for more
+# information on these settings.
+#
+#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.STABLE(desired_avg_gossip=20000):pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800):UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=false):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
+#cache.cluster.multicast.ip=231.12.21.132

src/core/etc/oscache.tld

+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+
+<taglib>
+	<tlib-version>1.6</tlib-version>
+	<jsp-version>1.1</jsp-version>
+	<short-name>oscache</short-name>
+    <description>OSCache - see http://www.opensymphony.com/oscache</description>
+
+	<tag>
+		<name>cache</name>
+        <tag-class>com.opensymphony.oscache.web.tag.CacheTag</tag-class>
+		<body-content>JSP</body-content>
+		<description>A tag to cache post-processed JSP contents</description>
+
+		<attribute>
+			<name>time</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>duration</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+        <attribute>
+            <name>cron</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+
+		<attribute>
+			<name>refreshpolicyclass</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>refreshpolicyparam</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>scope</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>refresh</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+        <attribute>
+            <name>mode</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+
+		<attribute>
+			<name>key</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+        <attribute>
+            <name>groups</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+
+		<attribute>
+			<name>language</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+	</tag>
+
+	<tag>
+		<name>usecached</name>
+		<tag-class>com.opensymphony.oscache.web.tag.UseCachedTag</tag-class>
+		<description>A tag to tell the cache to use the cached version</description>
+
+		<attribute>
+			<name>use</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+	</tag>
+
+	<tag>
+		<name>flush</name>
+		<tag-class>com.opensymphony.oscache.web.tag.FlushTag</tag-class>
+		<description>A tag to flush the cache</description>
+
+		<attribute>
+			<name>scope</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+		<attribute>
+			<name>key</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+        <attribute>
+            <name>group</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+		<attribute>
+			<name>language</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+		<attribute>
+			<name>pattern</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+	</tag>
+</taglib>

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

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.base.events.*;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+import com.opensymphony.oscache.util.StringUtil;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.*;
+
+import javax.swing.event.EventListenerList;
+
+/**
+ * An AbstractCacheAdministrator defines an abstract cache administrator, implementing all
+ * the basic operations related to the configuration of a cache, including assigning
+ * any configured event handlers to cache objects.<p>
+ *
+ * Extend this class to implement a custom cache administrator.
+ *
+ * @version        $Revision$
+ * @author        a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:fabian.crabus@gurulogic.de">Fabian Crabus</a>
+ * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
+ */
+public abstract class AbstractCacheAdministrator implements java.io.Serializable {
+    private static transient final Log log = LogFactory.getLog(AbstractCacheAdministrator.class);
+
+    /**
+     * A boolean cache configuration property that indicates whether the cache
+     * should cache objects in memory. Set this property to <code>false</code>
+     * to disable in-memory caching.
+     */
+    protected final static String CACHE_MEMORY_KEY = "cache.memory";
+
+    /**
+     * An integer cache configuration property that specifies the maximum number
+     * of objects to hold in the cache. Setting this to a negative value will
+     * disable the capacity functionality - there will be no limit to the number
+     * of objects that are held in cache.
+     */
+    protected final static String CACHE_CAPACITY_KEY = "cache.capacity";
+
+    /**
+     * A String cache configuration property that specifies the classname of
+     * an alternate caching algorithm. This class must extend
+     * {@link com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache}
+     * By default caches will use {@link com.opensymphony.oscache.base.algorithm.LRUCache} as
+     * the default algorithm if the cache capacity is set to a postive value, or
+     * {@link com.opensymphony.oscache.base.algorithm.UnlimitedCache} if the
+     * capacity is negative (ie, disabled).
+     */
+    protected final static String CACHE_ALGORITHM_KEY = "cache.algorithm";
+
+    /**
+     * A boolean cache configuration property that indicates whether the persistent
+     * cache should be unlimited in size, or should be restricted to the same size
+     * as the in-memory cache. Set this property to <code>true</code> to allow the
+     * persistent cache to grow without bound.
+     */
+    protected final static String CACHE_DISK_UNLIMITED_KEY = "cache.unlimited.disk";
+
+    /**
+     * The configuration key that specifies whether we should block waiting for new
+     * content to be generated, or just serve the old content instead. The default
+     * behaviour is to serve the old content since that provides the best performance
+     * (at the cost of serving slightly stale data).
+     */
+    protected final static String CACHE_BLOCKING_KEY = "cache.blocking";
+
+    /**
+     * A String cache configuration property that specifies the classname that will
+     * be used to provide cache persistence. This class must extend {@link PersistenceListener}.
+     */
+    protected static final String PERSISTENCE_CLASS = "cache.persistence.class";
+
+    /**
+     * A String cache configuration property that holds a comma-delimited list of
+     * classnames. These classes specify the event handlers that are to be applied
+     * to the cache.
+     */
+    protected static final String CACHE_ENTRY_EVENT_LISTENERS = "cache.event.listeners";
+    protected Config config = null;
+
+    /**
+     * Holds a list of all the registered event listeners. Event listeners are specified
+     * using the {@link #CACHE_ENTRY_EVENT_LISTENERS} configuration key.
+     */
+    protected EventListenerList listenerList = new EventListenerList();
+
+    /**
+     * The algorithm class being used, as specified by the {@link #CACHE_ALGORITHM_KEY}
+     * configuration property.
+     */
+    protected String algorithmClass = null;
+
+    /**
+     * The cache capacity (number of entries), as specified by the {@link #CACHE_CAPACITY_KEY}
+     * configuration property.
+     */
+    protected int cacheCapacity = -1;
+
+    /**
+     * Holds named cache objects that are managed by this class. Caches that
+     * are held in this registry are available for lookup by external code.
+     * This is useful when caches running in different JVMs or even different
+     * machines need to be coordinated. An event handler can pass through the name
+     * of the cache to the remote JVM, which can then retrieve the corresponding
+     * local cache with the same name.<p>
+     * For example the <code>BroadcastingCacheEventListener</code> uses this to
+     * flush cache entries across a cluster.
+     */
+    private Map namedCaches = new WeakHashMap();
+
+    /**
+     * Whether the cache blocks waiting for content to be build, or serves stale
+     * content instead. This value can be specified using the {@link #CACHE_BLOCKING_KEY}
+     * configuration property.
+     */
+    private boolean blocking = false;
+
+    /**
+     * Whether or not to store the cache entries in memory. This is configurable using the
+     * {@link com.opensymphony.oscache.base.AbstractCacheAdministrator#CACHE_MEMORY_KEY} property.
+     */
+    private boolean memoryCaching = true;
+
+    /**
+     * Whether the disk cache should be unlimited in size, or matched 1-1 to the memory cache.
+     * This can be set via the {@link #CACHE_DISK_UNLIMITED_KEY} configuration property.
+     */
+    private boolean unlimitedDiskCache;
+
+    /**
+     * Create the AbstractCacheAdministrator.
+     * This will initialize all values and load the properties from oscache.properties.
+     */
+    protected AbstractCacheAdministrator() {
+        this(null);
+    }
+
+    /**
+     * Create the AbstractCacheAdministrator.
+     *
+     * @param p the configuration properties for this cache.
+     */
+    protected AbstractCacheAdministrator(Properties p) {
+        loadProps(p);
+        initCacheParameters();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Constructed AbstractCacheAdministrator()");
+        }
+    }
+
+    /**
+     * Sets the algorithm to use for the cache.
+     *
+     * @see com.opensymphony.oscache.base.algorithm.LRUCache
+     * @see com.opensymphony.oscache.base.algorithm.FIFOCache
+     * @see com.opensymphony.oscache.base.algorithm.UnlimitedCache
+     * @param newAlgorithmClass The class to use (eg.
+     * <code>"com.opensymphony.oscache.base.algorithm.LRUCache"</code>)
+     */
+    public void setAlgorithmClass(String newAlgorithmClass) {
+        algorithmClass = newAlgorithmClass;
+    }
+
+    /**
+     * Indicates whether the cache will block waiting for new content to
+     * be built, or serve stale content instead of waiting. Regardless of this
+     * setting, the cache will <em>always</em> block if new content is being
+     * created, ie, there's no stale content in the cache that can be served.
+     */
+    public boolean isBlocking() {
+        return blocking;
+    }
+
+    /**
+     * Sets the cache capacity (number of items).
+     *
+     * @param newCacheCapacity The new capacity
+     */
+    public void setCacheCapacity(int newCacheCapacity) {
+        cacheCapacity = newCacheCapacity;
+    }
+
+    /**
+     * Are we caching entries in memory (true or false)?  Default is true.
+     * Set by the <code>cache.memory</code> property.
+     *
+     * @return Status whether or not memory caching is used.
+     */
+    public boolean isMemoryCaching() {
+        return memoryCaching;
+    }
+
+    /**
+     * Retrieves the value of one of the configuration properties.
+     *
+     * @param key The key assigned to the property
+     * @return Property value, or <code>null</code> if the property could not be found.
+     */
+    public String getProperty(String key) {
+        return config.getProperty(key);
+    }
+
+    /**
+     * Indicates whether the unlimited disk cache is enabled or not.
+     */
+    public boolean isUnlimitedDiskCache() {
+        return unlimitedDiskCache;
+    }
+
+    public Cache getNamedCache(String name) {
+        return (Cache) namedCaches.get(name);
+    }
+
+    protected void nameCache(String name, Cache cache) {
+        if (cache.getName() != null) {
+            log.error("nameCache() can only be called once.");
+            throw new UnsupportedOperationException("Cannot call nameCache() multiple times with the same cache object.");
+        }
+
+        cache.setName(name);
+
+        // Take a copy of the string since we're storing it in a WeakHashMap
+        namedCaches.put(new String(name), cache);
+    }
+
+    /**
+     * Retrieves an array containing instances all of the {@link CacheEventListener}
+     * classes that are specified in the OSCache configuration file.
+     */
+    protected CacheEventListener[] getCacheEventListeners() {
+        CacheEventListener[] listeners = null;
+
+        String[] classes = StringUtil.split(config.getProperty(CACHE_ENTRY_EVENT_LISTENERS), ',');
+        listeners = new CacheEventListener[classes.length];
+
+        for (int i = 0; i < classes.length; i++) {
+            try {
+                Class clazz = Class.forName(classes[i]);
+
+                if (!CacheEventListener.class.isAssignableFrom(clazz)) {
+                    log.error("Specified listener class '" + classes[i] + "' does not implement CacheEventListener. Ignoring this listener.");
+                } else {
+                    listeners[i] = (CacheEventListener) clazz.newInstance();
+                }
+            } catch (ClassNotFoundException e) {
+                log.error("CacheEventListener class '" + classes[i] + "' not found. Ignoring this listener.", e);
+            } catch (InstantiationException e) {
+                log.error("CacheEventListener class '" + classes[i] + "' could not be instantiated because it is not a concrete class. Ignoring this listener.", e);
+            } catch (IllegalAccessException e) {
+                log.error("CacheEventListener class '" + classes[i] + "' could not be instantiated because it is not public. Ignoring this listener.", e);
+            }
+        }
+
+        return listeners;
+    }
+
+    /**
+     * If there is a <code>PersistenceListener</code> in the configuration
+     * it will be instantiated and applied to the given cache object. If the
+     * <code>PersistenceListener</code> cannot be found or instantiated, an
+     * error will be logged but the cache will not have a persistence listener
+     * applied to it and no exception will be thrown.<p>
+     *
+     * A cache can only have one <code>PersistenceListener</code>.
+     *
+     * @param cache the cache to apply the <code>PersistenceListener</code> to.
+     *
+     * @return the same cache object that was passed in.
+     */
+    protected Cache setPersistenceListener(Cache cache) {
+        String persistenceClassname = config.getProperty(PERSISTENCE_CLASS);
+
+        try {
+            Class clazz = Class.forName(persistenceClassname);
+            PersistenceListener persistenceListener = (PersistenceListener) clazz.newInstance();
+
+            cache.setPersistenceListener(persistenceListener.configure(config));
+        } catch (ClassNotFoundException e) {
+            log.error("PersistenceListener class '" + persistenceClassname + "' not found. Check your configuration.", e);
+        } catch (Exception e) {
+            log.error("Error instantiating class '" + persistenceClassname + "'", e);
+        }
+
+        return cache;
+    }
+
+    /**
+     * Applies all of the recognised listener classes to the supplied
+     * cache object. Recognised classes are {@link CacheEntryEventListener}
+     * and {@link CacheMapAccessEventListener}.<p>
+     *
+     * @param cache The cache to apply the configuration to.
+     * @return cache The configured cache object.
+     */
+    protected Cache configureStandardListeners(Cache cache) {
+        if (config.getProperty(PERSISTENCE_CLASS) != null) {
+            cache = setPersistenceListener(cache);
+        }
+
+        if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS) != null) {
+            // Grab all the specified listeners and add them to the cache's
+            // listener list. Note that listeners that implement more than
+            // one of the event interfaces will be added multiple times.
+            CacheEventListener[] listeners = getCacheEventListeners();
+
+            for (int i = 0; i < listeners.length; i++) {
+                // Pass through the configuration to those listeners that require it
+                if (listeners[i] instanceof LifecycleAware) {
+                    try {
+                        ((LifecycleAware) listeners[i]).initialize(this, config);
+                    } catch (InitializationException e) {
+                        log.error("Could not initialize listener '" + listeners[i].getClass().getName() + "'. Listener ignored.", e);
+
+                        continue;
+                    }
+                }
+
+                if (listeners[i] instanceof CacheEntryEventListener) {
+                    cache.addCacheEventListener(listeners[i], CacheEntryEventListener.class);
+                }
+
+                if (listeners[i] instanceof CacheMapAccessEventListener) {
+                    cache.addCacheEventListener(listeners[i], CacheMapAccessEventListener.class);
+                }
+            }
+        }
+
+        return cache;
+    }
+
+    /**
+     * Finalizes all the listeners that are associated with the given cache object.
+     * Any <code>FinalizationException</code>s that are thrown by the listeners will
+     * be caught and logged.
+     */
+    protected void finalizeListeners(Cache cache) {
+        Object[] listeners = cache.listenerList.getListenerList();
+
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i + 1] instanceof LifecycleAware) {
+                try {
+                    ((LifecycleAware) listeners[i + 1]).finialize();
+                } catch (FinalizationException e) {
+                    log.error("Listener could not be finalized", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Initialize the core cache parameters from the configuration properties.
+     * The parameters that are initialized are:
+     * <ul>
+     * <li>the algorithm class ({@link #CACHE_ALGORITHM_KEY})</li>
+     * <li>the cache size ({@link #CACHE_CAPACITY_KEY})</li>
+     * <li>whether the cache is blocking or non-blocking ({@link #CACHE_BLOCKING_KEY})</li>
+     * <li>whether caching to memory is enabled ({@link #CACHE_MEMORY_KEY})</li>
+     * <li>whether the persistent cache is unlimited in size ({@link #CACHE_DISK_UNLIMITED_KEY})</li>
+     * </ul>
+     */
+    private void initCacheParameters() {
+        algorithmClass = getProperty(CACHE_ALGORITHM_KEY);
+
+        blocking = "true".equalsIgnoreCase(getProperty(CACHE_BLOCKING_KEY));
+
+        String cacheMemoryStr = getProperty(CACHE_MEMORY_KEY);
+
+        if ((cacheMemoryStr != null) && cacheMemoryStr.equalsIgnoreCase("false")) {
+            memoryCaching = false;
+        }
+
+        unlimitedDiskCache = Boolean.valueOf(config.getProperty(CACHE_DISK_UNLIMITED_KEY)).booleanValue();
+
+        String cacheSize = getProperty(CACHE_CAPACITY_KEY);
+
+        try {
+            if ((cacheSize != null) && (cacheSize.length() > 0)) {
+                cacheCapacity = Integer.parseInt(cacheSize);
+            }
+        } catch (NumberFormatException e) {
+            log.error("The value supplied for the cache capacity, '" + cacheSize + "', is not a valid number. The cache capacity setting is being ignored.");
+        }
+    }
+
+    /**
+     * Load the properties file from the classpath.
+     */
+    private void loadProps(Properties p) {
+        config = new Config(p);
+    }
+}

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

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+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.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 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 Cache implements Serializable {
+    /**
+     * 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();
+
+    /**
+     * An optional name for the cache. When a cache is named it gets remembered
+     * by the <code>AbstractCacheAdministrator</code>. It can then be looked up
+     * by listeners or other code that need to get hold of the named cache instance.
+     */
+    private String name = null;
+
+    /**
+     * 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
+     */
+    public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache) {
+        this(useMemoryCaching, unlimitedDiskCache, 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 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 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.setMemoryCaching(useMemoryCaching);
+
+        this.blocking = blocking;
+    }
+
+    /**
+     * Checks if the cache was flushed more recently than the CacheEntry provided.
+     * Used to determine whether to refresh the particular CacheEntry.
+     *
+     * @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;
+        }
+    }
+
+    /**
+     * Retrieve an object from the cache specifying its key.
+     *
+     * @param key             Key of the object in the cache.
+     * @param refreshPeriod   How long before the object needs refresh
+     *
+     * @return The object from cache
+     *
+     * @throws NeedsRefreshException Thrown when the object either
+     * doesn't exist, or exists but is stale. When this exception occurs,
+     * the CacheEntry corresponding to the supplied key will be locked
+     * and other threads requesting this entry will potentially be blocked
+     * until the caller repopulates the cache. If the caller choses not
+     * 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);
+    }
+
+    /**
+     * Retrieve an object from the cache specifying its key.
+     *
+     * @param key             Key of the object in the cache.
+     * @param refreshPeriod   How long before the object needs refresh
+     * @param cronExpiry      A cron expression that specifies fixed date(s)
+     *                        and/or time(s) that this cache entry should
+     *                        expire on.
+     *
+     * @return The object from cache
+     *
+     * @throws NeedsRefreshException Thrown when the object either
+     * doesn't exist, or exists but is stale. When this exception occurs,
+     * the CacheEntry corresponding to the supplied key will be locked
+     * and other threads requesting this entry will potentially be blocked
+     * until the caller repopulates the cache. If the caller choses not
+     * 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 cancelRefresh() 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;
+    }
+
+    /**
+     * Sets the name of the cache. This is optional, however only named
+     * caches are able to receive asynchronous flush notifications.
+     *
+     * @param name The name of the cache
+     */
+    void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Retrieves the name of the cache. The name is optional, so this
+     * could return <code>null</code>.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the listener to use for data persistence. Only one
+     * <code>PersistenceListener</code> can be configured per cache.
+     *
+     * @param listener The implementation of a persistance listener
+     */
+    public void setPersistenceListener(PersistenceListener listener) {
+        cacheMap.setPersistenceListener(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();
+    }
+
+    /**
+     * Register a listener for Cache events. The listener must implement
+     * one of the child interfaces of the {@link CacheEventListener} interface.
+     *
+     * @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.");
+        }
+    }
+
+    /**
+     * Cancels any pending update for this cache entry. This should <em>only</em>
+     * be called by the thread that is responsible for performing the update ie
+     * the thread that received the original {@link NeedsRefreshException}.<p/>
+     * If a cache entry is not updated (via {@link #putInCache} and this method is
+     * not called to let OSCache know the update will not be forthcoming, subsequent
+     * requests for this cache entry will either block indefinitely (if this is a new
+     * cache entry or cache.blocking=true), or forever get served stale content. Note
+     * however that there is no harm in cancelling an update on a key that either
+     * does not exist or is not currently being updated.
+     *
+     * @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.remove(key);
+
+                if (state != null) {
+                    synchronized (state) {
+                        state.cancelUpdate();
+                        state.notifyAll();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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;
+        dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, origin);
+    }
+
+    /**
+     * Flush the cache entry (if any) that corresponds to the cache key supplied.
+     * This call will flush the entry from the cache and remove the references to
+     * it from any cache groups that it is a member of. On completion of the flush,
+     * a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
+     *
+     * @param key The key of the entry to flush
+     */
+    public void flushEntry(String key) {
+        flushEntry(key, null);
+    }
+
+    /**
+     * Flush the cache entry (if any) that corresponds to the cache key supplied.
+     * This call will mark the cache entry as flushed so that the next access
+     * to it will cause a {@link NeedsRefreshException}. On completion of the
+     * flush, a <tt>CacheEntryEventType.ENTRY_FLUSHED</tt> event is fired.
+     *
+     * @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);
+    }
+
+    /**
+     * Flushes all objects that belong to the supplied group. On completion
+     * this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt> event.
+     *
+     * @param group The group to flush
+     */
+    public void flushGroup(String group) {
+        flushGroup(group, null);
+    }
+
+    /**
+     * Flushes all unexpired objects that belong to the supplied group. On
+     * completion this method fires a <tt>CacheEntryEventType.GROUP_FLUSHED</tt>
+     * event.
+     *
+     * @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);
+                }
+            }
+        }
+
+        dispatchCacheGroupEvent(CacheEntryEventType.GROUP_FLUSHED, group, origin);
+    }
+
+    /**
+     * Flush all entries with keys that match a given pattern
+     *
+     * @param  pattern The key must contain this given value
+     * @deprecated For performance and flexibility reasons it is preferable to
+     * 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);
+    }
+
+    /**
+     * Flush all entries with keys that match a given pattern
+     *
+     * @param  pattern The key must contain this given value
+     * @param origin The origin of this flush request
+     * @deprecated For performance and flexibility reasons it is preferable to
+     * 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);
+                    }
+                }
+            }
+
+            dispatchCachePatternEvent(CacheEntryEventType.PATTERN_FLUSHED, pattern, origin);
+        } else {
+            // Empty pattern, nothing to do
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Put an object in the cache specifying the key and refresh policy to use.
+     *
+     * @param key       Key of the object in the cache.
+     * @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);
+    }
+
+    /**
+     * Put in object into the cache, specifying both the key to use and the
+     * cache groups the object belongs to.
+     *
+     * @param key       Key of the object in the cache
+     * @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);
+    }
+
+    /**
+     * Put an object into the cache specifying both the key to use and the
+     * cache groups the object belongs to.
+     *
+     * @param key       Key of the object in the cache
+     * @param groups    The cache groups to add the object to
+     * @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();
+        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);
+
+        CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);
+
+        if (isNewEntry) {
+            dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
+        } else {
+            dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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 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) {
+                    state.completeUpdate();
+                    state.notifyAll();
+                }
+            }
+        }
+    }
+
+    /**
+     * Completely removes a cache entry from the cache and its associated cache
+     * groups.
+     *
+     * @param key The key of the entry to remove.
+     */
+    protected void removeEntry(String key) {
+        removeEntry(key, null);
+    }
+
+    /**
+     * 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);
+
+        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-wise event to all registered listeners.
+     *
+     * @param eventType The type of event (this is used to branch to the correct method handler)