[oscache] 01/02: New upstream version 2.4.1+ds1

Tony Mancill tmancill at moszumanska.debian.org
Sun Aug 6 16:02:52 UTC 2017


This is an automated email from the git hooks/post-receive script.

tmancill pushed a commit to branch master
in repository oscache.

commit 3e75b3e8d54717aac9e1b6de2731a15434e05ff9
Author: tony mancill <tmancill at debian.org>
Date:   Sun Aug 6 08:49:07 2017 -0700

    New upstream version 2.4.1+ds1
---
 .classpath                                         |   22 +
 .cvsignore                                         |    9 +
 .project                                           |   17 +
 LICENSE.txt                                        |   50 +
 OSCache.iml                                        |  161 ++
 OSCache.ipr                                        |  216 ++
 build.properties                                   |   10 +
 build.xml                                          |  390 +++
 docs/api.css                                       |   93 +
 docs/index.html                                    |   38 +
 docs/meta.xml                                      |  220 ++
 docs/navpanel.jsp                                  |   41 +
 docs/wiki/7147/CacheChainModel_v3.jpg              |  Bin 0 -> 77694 bytes
 docs/wiki/API Usage.html                           |   76 +
 docs/wiki/CacheFilter Tutorial.html                |  180 ++
 docs/wiki/CacheFilter.html                         |  118 +
 docs/wiki/Chain Caching Model.html                 |  127 +
 .../CacheChainModel_v1.pdf                         |  Bin 0 -> 6285 bytes
 .../CacheChainModel_v3.jpg                         |  Bin 0 -> 77694 bytes
 .../cache sequences.pdf                            |  Bin 0 -> 77990 bytes
 .../Chain Caching Model_attachments/oscache.fpr.gz |  Bin 0 -> 6557 bytes
 docs/wiki/Change Log.html                          |  122 +
 docs/wiki/Clustering.html                          |   79 +
 docs/wiki/Complete Change Log.html                 | 2959 ++++++++++++++++++++
 docs/wiki/Configuration.html                       |  134 +
 docs/wiki/Cron Expressions.html                    |   85 +
 docs/wiki/Documentation.html                       |   81 +
 docs/wiki/FAQ.html                                 |  174 ++
 docs/wiki/Feature List.html                        |  133 +
 docs/wiki/Hibernate 2.1 Cache Adapter.html         |  119 +
 docs/wiki/Hibernate 2.1 Cache Provider.html        |   96 +
 .../Hibernate 2.1 and pre OSCache 2.4 support.html |   68 +
 docs/wiki/Hibernate 3 Cache Adaptor.html           |  146 +
 docs/wiki/Hibernate 3 Cache Provider.html          |   94 +
 docs/wiki/Hibernate.html                           |  144 +
 docs/wiki/Home.html                                |   72 +
 docs/wiki/Installation Guide.html                  |  145 +
 docs/wiki/JMX Monitoring.html                      |   68 +
 docs/wiki/JSP Tags.html                            |  230 ++
 docs/wiki/License.html                             |   20 +
 docs/wiki/OSCache 1.0 beta 0.html                  |   29 +
 docs/wiki/OSCache 1.0 beta 1.html                  |   30 +
 docs/wiki/OSCache 1.0 beta 2.html                  |   29 +
 docs/wiki/OSCache 1.1.html                         |   31 +
 docs/wiki/OSCache 1.2.1.html                       |   29 +
 docs/wiki/OSCache 1.2.5.html                       |   28 +
 docs/wiki/OSCache 1.2.html                         |   27 +
 docs/wiki/OSCache 1.3.html                         |   30 +
 docs/wiki/OSCache 1.5.html                         |   34 +
 docs/wiki/OSCache 1.6.1.html                       |   32 +
 docs/wiki/OSCache 1.6.html                         |   31 +
 docs/wiki/OSCache 1.7.0.html                       |   56 +
 docs/wiki/OSCache 1.7.1.html                       |   38 +
 docs/wiki/OSCache 1.7.2.html                       |   30 +
 docs/wiki/OSCache 1.7.3.html                       |   29 +
 docs/wiki/OSCache 1.7.4.html                       |   28 +
 docs/wiki/OSCache 1.7.5.html                       |   28 +
 docs/wiki/OSCache 2.0 beta 1.html                  |   96 +
 docs/wiki/OSCache 2.0 beta 2.html                  |   44 +
 docs/wiki/OSCache 2.0.1.html                       |   46 +
 docs/wiki/OSCache 2.0.2.html                       |   41 +
 docs/wiki/OSCache 2.0.html                         |   41 +
 docs/wiki/OSCache 2.1.1.html                       |  289 ++
 docs/wiki/OSCache 2.1.html                         |  312 +++
 docs/wiki/OSCache 2.2 RC.html                      |  368 +++
 docs/wiki/OSCache 2.2.html                         |  226 ++
 docs/wiki/OSCache 2.3.1.html                       |  107 +
 docs/wiki/OSCache 2.3.2.html                       |  104 +
 docs/wiki/OSCache 2.3.html                         |  270 ++
 docs/wiki/OSCache 2.4.1.html                       |   97 +
 docs/wiki/OSCache 2.4.html                         |  496 ++++
 docs/wiki/OSCache in the Wild.html                 |   37 +
 docs/wiki/Requirements.html                        |   41 +
 docs/wiki/Roadmap.html                             |  372 +++
 docs/wiki/SVN and Compiling OSCache.html           |   37 +
 docs/wiki/Spring.html                              |   60 +
 docs/wiki/Statistics.html                          |  249 ++
 .../StatisticListenerImpl.java                     |  222 ++
 docs/wiki/What is OSCache.html                     |   55 +
 docs/wiki/border/border_bottom.gif                 |  Bin 0 -> 117 bytes
 docs/wiki/border/spacer.gif                        |  Bin 0 -> 43 bytes
 docs/wiki/icons/blogentry_16.gif                   |  Bin 0 -> 268 bytes
 docs/wiki/icons/bullet_blue.gif                    |  Bin 0 -> 60 bytes
 docs/wiki/icons/comment_16.gif                     |  Bin 0 -> 178 bytes
 docs/wiki/icons/emoticons/add.gif                  |  Bin 0 -> 599 bytes
 docs/wiki/icons/emoticons/biggrin.gif              |  Bin 0 -> 696 bytes
 docs/wiki/icons/emoticons/check.gif                |  Bin 0 -> 604 bytes
 docs/wiki/icons/emoticons/error.gif                |  Bin 0 -> 633 bytes
 docs/wiki/icons/emoticons/forbidden.gif            |  Bin 0 -> 613 bytes
 docs/wiki/icons/emoticons/help_16.gif              |  Bin 0 -> 634 bytes
 docs/wiki/icons/emoticons/information.gif          |  Bin 0 -> 1005 bytes
 docs/wiki/icons/emoticons/lightbulb.gif            |  Bin 0 -> 350 bytes
 docs/wiki/icons/emoticons/lightbulb_on.gif         |  Bin 0 -> 551 bytes
 docs/wiki/icons/emoticons/sad.gif                  |  Bin 0 -> 698 bytes
 docs/wiki/icons/emoticons/smile.gif                |  Bin 0 -> 699 bytes
 docs/wiki/icons/emoticons/star_blue.gif            |  Bin 0 -> 569 bytes
 docs/wiki/icons/emoticons/star_green.gif           |  Bin 0 -> 569 bytes
 docs/wiki/icons/emoticons/star_red.gif             |  Bin 0 -> 567 bytes
 docs/wiki/icons/emoticons/star_yellow.gif          |  Bin 0 -> 567 bytes
 docs/wiki/icons/emoticons/thumbs_down.gif          |  Bin 0 -> 283 bytes
 docs/wiki/icons/emoticons/thumbs_up.gif            |  Bin 0 -> 280 bytes
 docs/wiki/icons/emoticons/tongue.gif               |  Bin 0 -> 698 bytes
 docs/wiki/icons/emoticons/warning.gif              |  Bin 0 -> 569 bytes
 docs/wiki/icons/emoticons/wink.gif                 |  Bin 0 -> 698 bytes
 docs/wiki/icons/home_16.gif                        |  Bin 0 -> 594 bytes
 docs/wiki/icons/linkext7.gif                       |  Bin 0 -> 166 bytes
 docs/wiki/icons/mail_16.gif                        |  Bin 0 -> 381 bytes
 docs/wiki/icons/mail_small.gif                     |  Bin 0 -> 202 bytes
 docs/wiki/icons/user_12.gif                        |  Bin 0 -> 528 bytes
 docs/wiki/icons/user_16.gif                        |  Bin 0 -> 1008 bytes
 docs/wiki/index.html                               |  733 +++++
 docs/wiki/navpanel.jsp                             |    1 +
 docs/wiki/styles/site.css                          | 1511 ++++++++++
 ivy.xml                                            |   51 +
 ivyconf.properties                                 |    2 +
 ivyconf.xml                                        |   36 +
 lib/.cvsignore                                     |    1 +
 pom.xml                                            |  312 +++
 readme.txt                                         |   62 +
 src/etc/META-INF/taglib.tld                        |  152 +
 src/etc/findBugsExcludeFilter.xml                  |    5 +
 src/etc/jalopy.xml                                 |  378 +++
 src/etc/oscache.properties                         |  140 +
 .../oscache/base/AbstractCacheAdministrator.java   |  413 +++
 src/java/com/opensymphony/oscache/base/Cache.java  | 1014 +++++++
 .../com/opensymphony/oscache/base/CacheEntry.java  |  311 ++
 src/java/com/opensymphony/oscache/base/Config.java |  189 ++
 .../oscache/base/EntryRefreshPolicy.java           |   31 +
 .../oscache/base/EntryUpdateState.java             |  157 ++
 .../oscache/base/FinalizationException.java        |   23 +
 .../oscache/base/InitializationException.java      |   23 +
 .../opensymphony/oscache/base/LifecycleAware.java  |   42 +
 .../oscache/base/NeedsRefreshException.java        |   51 +
 .../algorithm/AbstractConcurrentReadCache.java     | 2157 ++++++++++++++
 .../oscache/base/algorithm/FIFOCache.java          |   92 +
 .../oscache/base/algorithm/LRUCache.java           |  156 ++
 .../oscache/base/algorithm/UnlimitedCache.java     |   71 +
 .../oscache/base/algorithm/package.html            |   37 +
 .../oscache/base/events/CacheEntryEvent.java       |   76 +
 .../base/events/CacheEntryEventListener.java       |   55 +
 .../oscache/base/events/CacheEntryEventType.java   |   54 +
 .../oscache/base/events/CacheEvent.java            |   48 +
 .../oscache/base/events/CacheEventListener.java    |   16 +
 .../oscache/base/events/CacheGroupEvent.java       |   71 +
 .../oscache/base/events/CacheMapAccessEvent.java   |   71 +
 .../base/events/CacheMapAccessEventListener.java   |   21 +
 .../base/events/CacheMapAccessEventType.java       |   37 +
 .../oscache/base/events/CachePatternEvent.java     |   69 +
 .../oscache/base/events/CachewideEvent.java        |   59 +
 .../oscache/base/events/CachewideEventType.java    |   26 +
 .../oscache/base/events/ScopeEvent.java            |   78 +
 .../oscache/base/events/ScopeEventListener.java    |   21 +
 .../oscache/base/events/ScopeEventType.java        |   33 +
 .../opensymphony/oscache/base/events/package.html  |   32 +
 .../com/opensymphony/oscache/base/package.html     |   31 +
 .../persistence/CachePersistenceException.java     |   29 +
 .../base/persistence/PersistenceListener.java      |   96 +
 .../oscache/base/persistence/package.html          |   31 +
 .../oscache/extra/CacheEntryEventListenerImpl.java |  195 ++
 .../extra/CacheMapAccessEventListenerImpl.java     |  111 +
 .../oscache/extra/ScopeEventListenerImpl.java      |  147 +
 .../oscache/extra/StatisticListenerImpl.java       |  295 ++
 .../com/opensymphony/oscache/extra/package.html    |   32 +
 .../oscache/general/GeneralCacheAdministrator.java |  307 ++
 .../com/opensymphony/oscache/general/package.html  |   31 +
 .../opensymphony/oscache/hibernate/OSCache.java    |  161 ++
 .../oscache/hibernate/OSCacheProvider.java         |  123 +
 .../opensymphony/oscache/hibernate/package.html    |   31 +
 .../AbstractBroadcastingListener.java              |  177 ++
 .../clustersupport/ClusterNotification.java        |   86 +
 .../clustersupport/JMS10BroadcastingListener.java  |  180 ++
 .../clustersupport/JMSBroadcastingListener.java    |  191 ++
 .../JavaGroupsBroadcastingListener.java            |  204 ++
 .../oscache/plugins/clustersupport/package.html    |   34 +
 .../AbstractDiskPersistenceListener.java           |  514 ++++
 .../diskpersistence/DiskPersistenceListener.java   |   53 +
 .../HashDiskPersistenceListener.java               |  113 +
 .../oscache/plugins/diskpersistence/package.html   |   31 +
 .../opensymphony/oscache/util/ClassLoaderUtil.java |   50 +
 .../opensymphony/oscache/util/FastCronParser.java  | 1042 +++++++
 .../com/opensymphony/oscache/util/StringUtil.java  |   67 +
 .../com/opensymphony/oscache/util/package.html     |   33 +
 .../oscache/web/CacheContextListener.java          |   40 +
 .../com/opensymphony/oscache/web/ServletCache.java |  118 +
 .../oscache/web/ServletCacheAdministrator.java     |  806 ++++++
 .../oscache/web/WebEntryRefreshPolicy.java         |   35 +
 .../oscache/web/filter/CacheFilter.java            |  782 ++++++
 .../filter/CacheHttpServletResponseWrapper.java    |  411 +++
 .../oscache/web/filter/ExpiresRefreshPolicy.java   |   75 +
 .../oscache/web/filter/ICacheGroupsProvider.java   |   33 +
 .../oscache/web/filter/ICacheKeyProvider.java      |   33 +
 .../oscache/web/filter/ResponseContent.java        |  237 ++
 .../web/filter/SplitServletOutputStream.java       |   94 +
 .../opensymphony/oscache/web/filter/package.html   |   33 +
 src/java/com/opensymphony/oscache/web/package.html |   31 +
 .../com/opensymphony/oscache/web/tag/CacheTag.java |  824 ++++++
 .../com/opensymphony/oscache/web/tag/FlushTag.java |  171 ++
 .../com/opensymphony/oscache/web/tag/GroupTag.java |   32 +
 .../opensymphony/oscache/web/tag/GroupsTag.java    |   33 +
 .../opensymphony/oscache/web/tag/UseCachedTag.java |   60 +
 .../com/opensymphony/oscache/web/tag/package.html  |   33 +
 src/java/overview.html                             |    6 +
 .../oscache/base/DummyAlwayRefreshEntryPolicy.java |   30 +
 .../base/GroupConcurrencyProblemTestCase.java      |   59 +
 .../base/TestAbstractCacheAdministrator.java       |   95 +
 .../com/opensymphony/oscache/base/TestCache.java   |  275 ++
 .../opensymphony/oscache/base/TestCacheEntry.java  |  136 +
 .../oscache/base/TestCompleteBase.java             |   63 +
 .../opensymphony/oscache/base/TestConcurrency.java |  489 ++++
 .../oscache/base/TestConcurrency2.java             |  480 ++++
 .../oscache/base/algorithm/TestAbstractCache.java  |  251 ++
 .../base/algorithm/TestCompleteAlgorithm.java      |   56 +
 .../oscache/base/algorithm/TestFIFOCache.java      |   73 +
 .../oscache/base/algorithm/TestLRUCache.java       |   80 +
 .../oscache/base/algorithm/TestQueueCache.java     |  229 ++
 .../oscache/base/algorithm/TestUnlimitedCache.java |   92 +
 .../oscache/base/events/TestCacheEntryEvent.java   |   75 +
 .../oscache/base/events/TestCacheGroupEvent.java   |   71 +
 .../base/events/TestCacheMapAccessEvent.java       |   53 +
 .../oscache/base/events/TestCachePatternEvent.java |   71 +
 .../oscache/base/events/TestCachewideEvent.java    |   57 +
 .../oscache/base/events/TestCompleteEvents.java    |   58 +
 .../oscache/base/events/TestScopeEvent.java        |   59 +
 .../extra/TestCacheEntryEventListenerImpl.java     |   90 +
 .../extra/TestCacheMapAccessEventListenerImpl.java |   71 +
 .../oscache/extra/TestCompleteExtra.java           |   56 +
 .../oscache/extra/TestScopeEventListenerImpl.java  |   62 +
 .../oscache/extra/TestStatisticListenerImpl.java   |   98 +
 .../oscache/general/TestCompleteGeneral.java       |   54 +
 .../oscache/general/TestConcurrent.java            |  126 +
 .../general/TestGeneralCacheAdministrator.java     |  400 +++
 .../BaseTestBroadcastingListener.java              |  117 +
 .../clustersupport/ListenForClusterTests.java      |  115 +
 .../clustersupport/TestCompleteClustering.java     |   53 +
 .../TestJMS10BroadcastingListener.java             |   58 +
 .../TestJMSBroadcastingListener.java               |   58 +
 .../TestJavaGroupsBroadcastingListener.java        |   52 +
 .../TestCompleteDiskPersistence.java               |   55 +
 .../TestDiskPersistenceListener.java               |  223 ++
 .../TestHashDiskPersistenceListener.java           |  220 ++
 .../diskpersistence/TestUnSerializable.java        |   89 +
 .../oscache/util/TestFastCronParser.java           |  314 +++
 .../opensymphony/oscache/web/CheckDeployment.java  |   46 +
 .../opensymphony/oscache/web/TestCompleteWeb.java  |   55 +
 .../oscache/web/TestLoadCompleteWeb.java           |   79 +
 .../oscache/web/TestOscacheFilter.java             |  215 ++
 .../opensymphony/oscache/web/TestOscacheJsp.java   |  208 ++
 .../oscache/web/TestOscacheServlet.java            |  194 ++
 src/test/java/oscacheDiskAndMemory.properties      |   11 +
 src/test/java/oscacheDiskOnly.properties           |    8 +
 src/test/java/oscacheDiskOnlyHash.properties       |    8 +
 .../java/oscacheMemoryAndOverflowToDisk.properties |   11 +
 src/test/java/oscacheMemoryOnly.properties         |    8 +
 .../opensymphony/oscache/web/OscacheServlet.java   |  130 +
 ...he-cachefilter-disableCacheOnMethods.properties |    8 +
 src/webapp/WEB-INF/classes/oscache.properties      |  140 +
 src/webapp/WEB-INF/web.xml                         |   60 +
 src/webapp/cachetest.jsp                           |   65 +
 src/webapp/cronTest.jsp                            |  172 ++
 src/webapp/filter/filterTest.jsp                   |   14 +
 .../filter2/filterTestDisableCacheOnMethods.jsp    |   23 +
 src/webapp/groupTest.jsp                           |   42 +
 src/webapp/index.html                              |   20 +
 src/webapp/oscache.txt                             |    1 +
 src/webapp/oscacheTest.jsp                         |   52 +
 src/webapp/oscacheTestMultipleTagNoKey.jsp         |  265 ++
 src/webapp/silentTest.jsp                          |   26 +
 www/index.html                                     |    6 +
 www/project_tools.html                             |    4 +
 269 files changed, 35120 insertions(+)

diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..341a72b
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src/java"/>
+	<classpathentry kind="src" output="build/test" path="src/test/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="lib/servletapi.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:file:/C:/Programme/Java/servlet-2_3-fcs-docs.zip!/"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/clover.jar"/>
+	<classpathentry kind="lib" path="lib/commons-logging.jar"/>
+	<classpathentry kind="lib" path="lib/httpunit.jar"/>
+	<classpathentry kind="lib" path="lib/jms.jar"/>
+	<classpathentry kind="lib" path="lib/junit.jar"/>
+	<classpathentry kind="lib" path="lib/jgroups-all.jar"/>
+	<classpathentry kind="lib" path="lib/hibernate3.jar" sourcepath="C:/Programme/Java/hibernate-3.2/src"/>
+	<classpathentry kind="lib" path="lib/backport-util-concurrent.jar" sourcepath="C:/Programme/Java/backport-util-concurrent-3.0/src"/>
+	<classpathentry kind="lib" path="lib/junitperf-1.9.1.jar"/>
+	<classpathentry kind="lib" path="lib/GroboUtils-5-core.jar"/>
+	<classpathentry kind="output" path="build/java"/>
+</classpath>
diff --git a/.cvsignore b/.cvsignore
new file mode 100644
index 0000000..18b8b59
--- /dev/null
+++ b/.cvsignore
@@ -0,0 +1,9 @@
+build
+build.test
+build.properties
+dist
+*.i*
+.settings
+.fbprefs
+.fbwarnings
+.clover
diff --git a/.project b/.project
new file mode 100644
index 0000000..01e1d91
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>oscache</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..66c65e6
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,50 @@
+/* ====================================================================
+ * The OpenSymphony Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        OpenSymphony Group (http://www.opensymphony.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "OpenSymphony" and "The OpenSymphony Group"
+ *    must not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact license at opensymphony.com .
+ *
+ * 5. Products derived from this software may not be called "OpenSymphony"
+ *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
+ *    name, without prior written permission of the OpenSymphony Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
diff --git a/OSCache.iml b/OSCache.iml
new file mode 100644
index 0000000..4bbd147
--- /dev/null
+++ b/OSCache.iml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4" relativePaths="true" type="J2EE_WEB_MODULE" j2ee-integration="Tomcat Server">
+  <component name="ModuleRootManager" />
+  <component name="NewModuleRootManager">
+    <output url="file://$MODULE_DIR$/build/java" />
+    <exclude-output />
+    <exclude-exploded />
+    <output-test url="file://$MODULE_DIR$/build/test/java" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="false" />
+      <excludeFolder url="file://$MODULE_DIR$/build" />
+      <excludeFolder url="file://$MODULE_DIR$/dist" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Default" level="application_server_libraries" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/commons-collections.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/jms.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/commons-logging.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/servletapi.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/clover.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/junit.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/jgroups-all.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/httpunit.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/junitperf.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntryProperties />
+  </component>
+  <component name="WebModuleBuildComponent">
+    <setting name="EXPLODED_URL" value="file://" />
+    <setting name="EXPLODED_ENABLED" value="false" />
+    <setting name="JAR_URL" value="file://" />
+    <setting name="JAR_ENABLED" value="false" />
+    <setting name="SYNC_EXPLODED_DIR" value="true" />
+    <setting name="BUILD_ON_FRAME_DEACTIVATION" value="false" />
+    <setting name="RUN_JASPER_VALIDATION" value="false" />
+  </component>
+  <component name="WebModuleProperties">
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/clover.jar" />
+      <url>jar://$MODULE_DIR$/lib/clover.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/commons-collections.jar" />
+      <url>jar://$MODULE_DIR$/lib/commons-collections.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/commons-logging.jar" />
+      <url>jar://$MODULE_DIR$/lib/commons-logging.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/httpunit.jar" />
+      <url>jar://$MODULE_DIR$/lib/httpunit.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/jgroups-all.jar" />
+      <url>jar://$MODULE_DIR$/lib/jgroups-all.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/jms.jar" />
+      <url>jar://$MODULE_DIR$/lib/jms.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/junit.jar" />
+      <url>jar://$MODULE_DIR$/lib/junit.jar!/</url>
+    </containerElement>
+    <containerElement type="library" level="module">
+      <attribute name="method" value="1" />
+      <attribute name="URI" value="/WEB-INF/lib/junitperf.jar" />
+      <url>jar://$MODULE_DIR$/lib/junitperf.jar!/</url>
+    </containerElement>
+    <deploymentDescriptor name="web.xml" url="file://$MODULE_DIR$/src/webapp/WEB-INF/web.xml" version="2.3" />
+    <webroots>
+      <root url="file://$MODULE_DIR$/src/webapp" relative="/" />
+      <root url="file://$MODULE_DIR$/src/webapp/WEB-INF" relative="/WEB-INF" />
+    </webroots>
+  </component>
+  <component name="WeblogicWebModuleProperties">
+    <option name="IS_RUN_APPC" value="false" />
+  </component>
+</module>
+
diff --git a/OSCache.ipr b/OSCache.ipr
new file mode 100644
index 0000000..a509dd4
--- /dev/null
+++ b/OSCache.ipr
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" relativePaths="false">
+  <component name="AntConfiguration">
+    <defaultAnt bundledAnt="true" />
+    <buildFile url="file://$PROJECT_DIR$/build.xml">
+      <additionalClassPath>
+        <entry path="file://$os$/opensymphony/common/clover-license.jar" />
+        <entry path="file://$os$/opensymphony/common/commons-cli.jar" />
+        <entry path="file://$os$/opensymphony/common/commons-httpclient.jar" />
+        <entry path="file://$os$/opensymphony/common/commons-logging.jar" />
+        <entry path="file://$os$/opensymphony/common/ivy-1.1.jar" />
+        <entry path="file://$PROJECT_DIR$/lib/clover.jar" />
+        <entry path="file://$PROJECT_DIR$/lib/junit.jar" />
+      </additionalClassPath>
+      <antReference projectDefault="true" />
+      <customJdkName value="" />
+      <maximumHeapSize value="128" />
+      <properties />
+    </buildFile>
+  </component>
+  <component name="CodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS" />
+    <option name="USE_PER_PROJECT_SETTINGS" value="false" />
+  </component>
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <option name="CLEAR_OUTPUT_DIRECTORY" value="true" />
+    <option name="DEPLOY_AFTER_MAKE" value="0" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="?*.properties" />
+      <entry name="?*.xml" />
+      <entry name="?*.gif" />
+      <entry name="?*.png" />
+      <entry name="?*.jpeg" />
+      <entry name="?*.jpg" />
+      <entry name="?*.html" />
+      <entry name="?*.dtd" />
+      <entry name="?*.tld" />
+    </wildcardResourcePatterns>
+  </component>
+  <component name="DataSourceManagerImpl" />
+  <component name="DependenciesAnalyzeManager">
+    <option name="myForwardDirection" value="false" />
+  </component>
+  <component name="DependencyValidationManager" />
+  <component name="EntryPointsManager">
+    <entry_points />
+  </component>
+  <component name="ExportToHTMLSettings">
+    <option name="PRINT_LINE_NUMBERS" value="false" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+    <option name="OUTPUT_DIRECTORY" />
+  </component>
+  <component name="GUI Designer component loader factory" />
+  <component name="JavacSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="DEPRECATION" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="JikesSettings">
+    <option name="JIKES_PATH" value="" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="DEPRECATION" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="IS_EMACS_ERRORS_MODE" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+    </group>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/OSCache.iml" filepath="$PROJECT_DIR$/OSCache.iml" />
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="false" project-jdk-name="JDK 1.4.2" />
+  <component name="RmicSettings">
+    <option name="IS_EANABLED" value="false" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="GENERATE_IIOP_STUBS" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="libraryTable" />
+  <component name="uidesigner-configuration">
+    <option name="INSTRUMENT_CLASSES" value="true" />
+    <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+  </component>
+  <UsedPathMacros>
+    <macro name="os" />
+  </UsedPathMacros>
+</project>
+
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000..b9e2e25
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,10 @@
+# OSCache build properties
+name=oscache
+fullname=OSCache
+version=2.4.1
+status=integration
+cvs.tag=v2_4_1
+
+# The URL to use for testing the example webapp. Comment this out to disable the webapp tests.
+#test.web.baseURL=http://localhost:7001/oscache-example/
+#test.web.deployDir=C:/Programme/bea/user_projects/domains/mydomain/applications
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..40de97d
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,390 @@
+<?xml version="1.0" ?>
+
+<!-- OSCache build file - http://www.opensymphony.com/oscache -->
+<project name="oscache" default="jar" basedir=".">
+
+    <!-- overridden properties (must be before the import!) -->
+   
+    <property name="compile.version" value="1.4"/>
+    <property name="test.compile.version" value="1.4"/>
+
+    <property name="lib.optional" value="${lib}/plugins"/> <!-- overwritten -->
+    <property name="jar.excludes" value="test/**, docs/**, *.war, **/*.html"/>
+
+    <property name="tmp.dir" value="tmp"/> <!-- ??? -->
+
+    <!-- This property must match with what is included in the oscache.properties files -->
+    <property name="test.cache.path" value="/tmp/cachetagscache"/>
+
+    <!-- debug -->   
+    <property name="debug" value="true"/>
+
+    <!-- import common osbuild.xml -->
+    
+    <property name="common.build" value="../opensymphony/common/osbuild.xml"/>
+    <import file="${common.build}"/>
+ 
+    <property name="clover.initstring" location="${build.clover}/coverageBase.db"/>
+    
+    <!-- project properties -->
+
+    <property name="src.webapp" value="${src}/webapp"/> <!-- new -->
+ 
+    <patternset id="src.test.pattern.base">
+        <include name="**/TestComplete*.class"/>
+        <exclude name="**/web/*.*"/>
+        <exclude name="**/clustersupport/*.*"/>
+    </patternset>
+
+    <patternset id="src.test.pattern.web">
+        <include name="**/web/TestComplete*.class"/>
+        <include name="**/web/TestLoadComplete*.class"/>
+    </patternset>
+
+    <patternset id="src.test.pattern.cluster">
+        <include name="**/TestCompleteCluster.class"/>
+    </patternset>
+   
+    <!-- init -->
+    
+    <target name="init" depends="ivy-check,common.init">
+        <taskdef name="ivy-configure" classname="fr.jayasoft.ivy.ant.IvyConfigure"/>
+        <taskdef name="ivy-resolve" classname="fr.jayasoft.ivy.ant.IvyResolve"/>
+        <taskdef name="ivy-retrieve" classname="fr.jayasoft.ivy.ant.IvyRetrieve"/>
+        <taskdef name="ivy-publish" classname="fr.jayasoft.ivy.ant.IvyPublish"/>
+        <taskdef name="ivy-report" classname="fr.jayasoft.ivy.ant.IvyReport"/>
+        <taskdef name="ivy-deliver" classname="fr.jayasoft.ivy.ant.IvyDeliver"/>
+
+        <ivy-retrieve/>
+    </target>
+ 
+    <!-- Ivy -->   
+    
+    <target name="ivyrep.copy-ivy" depends="init">
+        <ivy-deliver deliverpattern="${ivyrep.path}/opensymphony/${name}/[artifact]-[revision].[ext]"
+                     pubrevision="${version}-${TIME}" pubdate="${TIME}"/>
+    </target>
+
+    <available property="ivy.available" classname="fr.jayasoft.ivy.ant.IvyRetrieve"/>
+   
+    <target name="ivy-check" unless="ivy.available">
+        <fail message="Please download Ivy at http://www.jayasoft.org/ivy and copy ivy.jar to ${ant.home}${file.separator}lib"/>
+    </target>
+   
+    <!-- Prepares the build directory -->
+    <target name="prepare" depends="init">
+        <mkdir dir="${build.java}/META-INF"/>
+        <copy file="${src}/etc/META-INF/taglib.tld" tofile="${build.java}/META-INF/taglib.tld"/>
+    </target>
+
+
+    <!-- Compiles the core source code -->
+    <target name="compile" depends="prepare">
+        <javac srcdir="${src.java}" destdir="${build.java}" includes="com/opensymphony/oscache/**" debug="${debug}" classpathref="cp" source="${compile.version}" target="${compile.version}"/>
+    </target>
+
+
+    <!-- Prepares and compiles the web application, which includes the web test suite -->
+    <target name="example-war" depends="jar">
+        <mkdir dir="${build}/webapp"/>
+        <mkdir dir="${dist}"/>
+
+        <javac srcdir="${src.webapp}/WEB-INF/classes" destdir="${build}/webapp" includes="com/opensymphony/oscache/**" debug="${debug}" classpath="${build.java}" classpathref="cp"/>
+
+        <war destfile="${dist}/${name}-example.war" basedir="${src.webapp}" webxml="${src.webapp}/WEB-INF/web.xml" excludes="WEB-INF/web.xml">
+            <lib file="${build}/${name}-${version}.jar"/>
+            <lib file="${lib}/commons-logging.jar"/>
+            <classes dir="${build}/webapp"/>
+        </war>
+    </target>
+
+    
+    <!-- Deploy example war for the test web -->
+    <target name="deploy-example-war" depends="example-war" if="test.web.deployDir">
+        <!-- Use auto deployment of app server, e.g. BEA WLS -->   
+        <copy file="${dist}/${name}-example.war" tofile="${test.web.deployDir}/${name}-example.war" overwrite="yes"/>
+        <!-- if your pc is to fast, sleep here for a while to allow redeployment of the web app -->
+        <echo message="Deploying example web app and waiting for a while..." level="info"/>
+        <sleep seconds="30"/> 
+    </target>
+
+    
+    <!-- Build a usable jar file -->
+    <target name="jar" depends="compile">
+        <mkdir dir="${build}"/>
+
+        <jar jarfile="${build}/${name}-${version}.jar" basedir="${build.java}" excludes="${jar.excludes}">
+            <manifest>
+                <attribute name="Implementation-Title" value="${fullname}"/>
+                <attribute name="Implementation-Version" value="${version}"/>
+                <attribute name="Implementation-Vendor" value="OpenSymphony"/>
+            </manifest>
+        </jar>
+    </target>
+ 
+ 
+    <!-- macrodef for tests -->
+    
+    <macrodef name="testBase">
+        <attribute name="file"/>
+        <attribute name="message"/>
+        <sequential>
+            <echo message="@{message}" level="info"/>
+            <copy file="@{file}" tofile="${build.test}/oscache.properties" overwrite="yes"/>
+         
+            <!-- Clear out any previous persistent cache directory -->
+            <delete dir="${test.cache.path}" failonerror="false"/>
+
+            <junit printsummary="yes" haltonfailure="no" haltonerror="yes" fork="yes" failureproperty="test.failure">
+                <classpath>
+                    <pathelement location="${build.test}"/>
+                    <path refid="cp"/>
+                </classpath>
+
+                <formatter type="xml"/>
+
+                <batchtest todir="${dist.docs}/junit">
+                    <fileset dir="${build.test}">
+                        <patternset refid="src.test.pattern.base"/>
+                    </fileset>
+                </batchtest>
+            </junit>
+         
+            <!-- Clear out persistent cache directory -->
+            <delete dir="${test.cache.path}" failonerror="false"/>
+   
+        </sequential>
+    </macrodef>
+    
+    <macrodef name="testWeb">
+        <attribute name="file"/>
+        <attribute name="message"/>
+        <sequential>
+            <echo message="@{message}" level="info"/>
+            <copy file="@{file}" tofile="${build.test}/oscache.properties" overwrite="yes"/>
+         
+            <!-- Clear out any previous persistent cache directory -->
+            <delete dir="${test.cache.path}" failonerror="false"/>
+
+            <javac srcdir="${src.test}" destdir="${build.test}" includes="com/opensymphony/oscache/web/**" debug="${debug}" classpath="${build}" classpathref="cp"/>
+
+            <java classname="com.opensymphony.oscache.web.CheckDeployment" failonerror="true" classpath="${build.test}" fork="yes">
+                <arg value="${test.web.baseURL}"/>
+            </java>
+
+            <junit printsummary="yes" haltonfailure="no" haltonerror="yes" fork="yes" failureproperty="test.failure">
+                <sysproperty key="test.web.baseURL" value="${test.web.baseURL}"/>
+                <classpath>
+                    <pathelement location="${build.test}"/>
+                    <pathelement location="${build}"/>
+                    <path refid="cp"/>
+                </classpath>
+
+                <formatter type="xml"/>
+                <formatter type="plain" useFile="false"/>
+                
+                <batchtest todir="${dist.docs}/junit">
+                    <fileset dir="${build.test}">
+                        <patternset refid="src.test.pattern.web"/>
+                    </fileset>
+                </batchtest>
+            </junit>
+            
+            <!-- Clear out persistent cache directory -->
+            <delete dir="${test.cache.path}" failonerror="false"/>
+            
+        </sequential>
+    </macrodef>
+
+    <!-- Compiling sources for junit tests and clover -->   
+    <target name="test-compile" depends="junit-check, clover-check">
+        <mkdir dir="${dist.docs}/junit"/>
+        <mkdir dir="${dist.docs}/clover"/>
+        <mkdir dir="${build.test}"/>
+        <mkdir dir="${build.clover}"/>
+        <mkdir dir="${build.clover}/history" />
+
+	    <taskdef resource="clovertasks"/>
+        <taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask"/>
+	
+        <javac destdir="${build.test}" debug="${debug}" classpathref="cp" source="${test.compile.version}" target="${test.compile.version}" compiler="org.apache.tools.ant.taskdefs.CloverCompilerAdapter">
+            <src path="${src.java}"/>
+        </javac>
+
+	    <javac destdir="${build.test}" debug="${debug}" classpathref="cp" source="${test.compile.version}" target="${test.compile.version}">
+            <src path="${src.test}"/>
+        </javac>
+    </target>
+
+    <!-- Run base tests -->   
+    <target name="test-base-mc" depends="test-compile" unless="test.failure" description="run base tests">
+        <!-- Run tests using Memory Cache Only -->
+        <testBase file="${src.test}/oscacheMemoryOnly.properties" message="Running base tests with memory cache only"/>
+    </target>
+
+    <target name="test-base-dc" depends="test-compile" unless="test.failure" description="run base tests">
+        <!-- Rerun tests using Disk Cache Only -->
+        <testBase file="${src.test}/oscacheDiskOnly.properties" message="Running base tests with disk cache only"/>
+    </target>
+
+    <target name="test-base-dc-hash" depends="test-compile" unless="test.failure" description="run base tests">
+        <!-- Rerun tests using Disk Cache Only -->
+        <testBase file="${src.test}/oscacheDiskOnlyHash.properties" message="Running base tests with disk cache hash persistence only"/>
+    </target>
+
+    <target name="test-base-dmc" depends="test-compile" unless="test.failure" description="run base tests">
+        <!-- ReRun tests using Disk and Memory Cache -->
+        <testBase file="${src.test}/oscacheDiskAndMemory.properties" message="Running base tests with disk and memory caches"/>
+    </target>
+
+    <target name="test-base-mdoc" depends="test-compile" unless="test.failure" description="run base tests">
+        <!-- ReRun tests using Memory and Disk Overflow Cache -->   
+        <testBase file="${src.test}/oscacheMemoryAndOverflowToDisk.properties" message="Running base tests with memory and disk overflow caches"/>
+    </target>
+
+    <target name="test-base" depends="test-base-mc, test-base-dc, test-base-dc-hash, test-base-dmc, test-base-mdoc" description="run all base tests"/>
+   
+    <!-- Run web tests -->   
+    <target name="test-web-mc" depends="test-compile, example-war, deploy-example-war" unless="test.failure" if="test.web.baseURL" description="run web tests">
+        <!-- Run tests using Memory Cache Only -->
+        <testWeb file="${src.test}/oscacheMemoryOnly.properties" message="Running web tests with memory cache only"/>
+    </target>
+
+    <target name="test-web-dc" depends="test-compile, example-war, deploy-example-war" unless="test.failure" if="test.web.baseURL" description="run web tests">
+        <!-- Rerun tests using Disk Cache Only -->
+        <testWeb file="${src.test}/oscacheDiskOnly.properties" message="Running web tests with disk cache only"/>
+    </target>
+
+    <target name="test-web-dc-hash" depends="test-compile, example-war, deploy-example-war" unless="test.failure" if="test.web.baseURL" description="run web tests">
+        <!-- Rerun tests using Disk Cache Only -->
+        <testWeb file="${src.test}/oscacheDiskOnlyHash.properties" message="Running web tests with disk cache hash persistence only"/>
+    </target>
+
+    <target name="test-web-dmc" depends="test-compile, example-war, deploy-example-war" unless="test.failure" if="test.web.baseURL" description="run web tests">
+        <!-- ReRun tests using Disk and Memory Cache -->
+        <testWeb file="${src.test}/oscacheDiskAndMemory.properties" message="Running web tests with disk and memory caches"/>
+    </target>
+
+    <target name="test-web-mdoc" depends="test-compile, example-war, deploy-example-war" unless="test.failure" if="test.web.baseURL" description="run web tests">
+        <!-- ReRun tests using Memory and Disk Overflow Cache -->   
+        <testWeb file="${src.test}/oscacheMemoryAndOverflowToDisk.properties" message="Running web tests with memory and disk overflow caches"/>
+    </target>
+    
+    <target name="test-web" depends="test-web-mc, test-web-dc, test-web-dc-hash, test-web-dmc, test-web-mdoc" description="run all web tests"/>
+   
+    <!-- Run clustering tests -->
+    <target name="test-cluster" depends="test-compile" unless="test.failure" if="test-cluster" description="run cluster tests">
+        <echo message="Running tests with memory caches and clustering" level="info"/>
+     
+        <!-- Clear out any previous persistent cache directory -->
+        <delete dir="${test.cache.path}" failonerror="false"/>
+
+        <junit printsummary="yes" haltonfailure="no" haltonerror="yes" fork="yes" failureproperty="test.failure">
+            <classpath>
+                <pathelement location="${build.test}"/>
+                <path refid="cp"/>
+            </classpath>
+
+            <formatter type="xml"/>
+
+            <batchtest todir="${dist.docs}/junit">
+                <fileset dir="${build.test}">
+                    <patternset refid="src.test.pattern.cluster"/>
+                </fileset>
+            </batchtest>
+        </junit>
+     
+        <!-- Clear out persistent cache directory -->
+        <delete dir="${test.cache.path}" failonerror="false"/>
+    </target>
+
+    
+    <!-- Run JUnit tests using different combinations of disk and memory caching -->
+    <target name="test" depends="test-base, test-web, test-cluster" description="run all tests"/>
+ 
+    
+    <!-- Create the distribution zip files -->
+    <target name="dist" depends="jar, docs, example-war">
+        <!-- copy the standard file -->
+        <copy file="${build}/${name}-${version}.jar" tofile="${dist}/${name}-${version}.jar"/>
+   
+        <!-- create the full package -->
+        <mkdir dir="${tmp.dir}/docs"/>
+        <mkdir dir="${tmp.dir}/src"/>
+        <mkdir dir="${tmp.dir}/lib"/>
+        <mkdir dir="${tmp.dir}/etc"/>
+
+        <copy todir="${tmp.dir}/docs">
+            <fileset dir="${dist.docs}"/>
+        </copy>
+        <copy todir="${tmp.dir}/src">
+            <fileset dir="${src}" excludes="etc/**"/>
+        </copy>
+        
+        <copy file="${lib}/commons-logging.jar" todir="${tmp.dir}/lib"/>
+        <copy file="${lib}/jgroups-all.jar" todir="${tmp.dir}/lib"/>
+
+        <copy file="${build}/${name}-${version}.jar" todir="${tmp.dir}"/>
+        
+        <copy file="${src}/etc/oscache.properties" tofile="${tmp.dir}/etc/oscache.properties"/>
+        <copy file="${src}/etc/META-INF/taglib.tld" tofile="${tmp.dir}/etc/META-INF/${name}.tld"/>
+        <copy file="readme.txt" tofile="${tmp.dir}/readme.txt" failonerror="false"/>
+
+        <zip zipfile="${dist}/${name}-${version}-full.zip" basedir="${tmp.dir}" includes="**"/>
+
+        <!-- Remove everything that's not in the binary release -->
+        <delete dir="${tmp.dir}/src"/>
+        <delete dir="${tmp.dir}/${lib.build}"/>
+        <delete dir="${tmp.dir}/docs/junit"/>
+        <delete dir="${tmp.dir}/docs/clover"/>
+        <delete file="${tmp.lib}/jgroups-all.jar"/>
+
+        <zip zipfile="${dist}/${name}-${version}-binary.zip" basedir="${tmp.dir}" includes="**"/>
+
+        <delete dir="${tmp.dir}"/>
+     
+        <!-- Creates checksum for the distribution files -->   
+        <checksum>
+          <fileset dir="${dist}">
+            <include name="*.jar"/>
+            <include name="*.zip"/>
+            <include name="*.war"/>
+          </fileset>
+        </checksum>
+
+    </target>
+
+    <target name="clover.report" depends="test">
+        <clover-report>
+            <current outfile="${dist.docs}/clover">
+                <fileset dir="${src.java}" excludes="**/Test*"/>
+                <format type="html"/>
+            </current>
+        </clover-report>
+<!--
+        <clover-historypoint historyDir="${build.clover}/history" />
+
+        <clover-report>
+            <historical outfile="${dist.docs}/clover/historical.pdf" historyDir="${build.clover}/history" />
+        </clover-report>
+-->        
+    </target>
+	
+    <target name="junit.report" depends="test">
+        <junitreport todir="${dist.docs}/junit">
+            <fileset dir="${dist.docs}/junit">
+                <include name="TEST-*.xml"/>
+            </fileset>
+            <report format="frames" todir="${dist.docs}/junit"/>
+        </junitreport>
+     
+        <!-- we fail here (instead of in the test target) so that the reports get generated first) -->
+        <fail if="test.failure" message="Tests did not all pass, failing!"/>
+    </target>
+
+    <target name="reports" depends="common.reports">
+        <mkdir dir="${dist.docs}/dependencies"/>
+        <ivy-report todir="${dist.docs}/dependencies" graph="false"/>
+    </target>
+    
+</project>
\ No newline at end of file
diff --git a/docs/api.css b/docs/api.css
new file mode 100644
index 0000000..a718017
--- /dev/null
+++ b/docs/api.css
@@ -0,0 +1,93 @@
+td, li, br, div, p
+{
+	font-family : verdana, arial, helvetica, sans-serif;
+	font-size: 11px;
+}
+
+body
+{
+  margin:  0;
+  padding:  0;
+  color:  #333333;
+  background-color: white;
+  font-size: 11px;
+  font-family: verdana, arial, helvetica, sans-serif;
+  line-height: 19px;
+}
+
+TD.NavBarCell1
+{
+  font-size: 10px;
+  font-family: Verdana, Sans-Serif;
+  font-weight: bold;
+  color: #666666;
+  background-color: #99CCFF;
+}
+
+TD.NavBarCell1Rev
+{
+  font-family: Verdana, Sans-Serif;
+  font-size: 11px;
+  font-weight: bold;
+  color: #dedede;
+  background-color: #669900;
+}
+
+TR.TableHeadingColor
+{
+	background-color: #99CCFF;
+}
+
+HR
+{
+	size: 1;
+	color: #000000;
+	height: 1;
+}
+
+a
+{
+  text-decoration: none;
+}
+
+a:link
+{
+  color: #003366;
+}
+a:visited
+{
+  color: #cc6600;
+}
+a:active
+{
+  background-color: transparent;
+  color: #cc6600;
+}
+
+li
+{
+	padding-bottom: 2pt;
+}
+
+h2
+{
+  color: #006699;
+  margin-bottom: 10px;
+  margin-top: 10px;
+}
+
+h3
+{
+  color: #006699;
+}
+
+h4
+{
+  font: 11px verdana, arial, helvetica, sans-serif;
+  font-weight: bold;
+  color: #cc6600;
+}
+h5, h6
+{
+  color: #032588;
+}
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..d384729
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,38 @@
+<html>
+	<head>
+		<title>OSCache</title>
+	</head>
+
+	<body>
+
+<p/>
+
+OSCache is a caching solution that includes a JSP tag library and set of classes to perform fine grained dynamic caching of JSP content,
+ servlet responses or arbitrary objects. It provides both in memory and persistent on disk caches, and can allow your site 
+ to have graceful error tolerance (eg if an error occurs like your db goes down, you can serve the cached content so people 
+ can still surf the site almost without knowing). Take a look at the great <a href="wiki/Feature%20List.html">features</a> of OSCache.
+
+<p/>
+
+Here you will find documentation and download information for the latest version of OSCache.
+
+<p/>
+
+<ul>
+    <li><a href="wiki/Documentation.html">Documentation</a></li>
+    <li><a href="api/index.html">JavaDoc API</a></li>
+</ul>
+Reports:
+<ul>
+    <li><a href="junit/index.html">Unit Test Results</a></li>
+    <li><a href="clover/index.html">Clover Coverage Report</a></li>
+    <li><a href="dependencies/opensymphony-oscache-default.html">Ivy Dependency Report</a></li>
+</ul>
+
+<p/>
+
+Documentation for the currently developing release can always be found on the <a href="http://wiki.opensymphony.com/display/CACHE">OSCache Wiki</a>.
+ If you have edits or corrections to make to the documentation here, you may edit them directly on the wiki as well.
+
+	</body>
+</html>
\ No newline at end of file
diff --git a/docs/meta.xml b/docs/meta.xml
new file mode 100644
index 0000000..4e782ff
--- /dev/null
+++ b/docs/meta.xml
@@ -0,0 +1,220 @@
+<project>
+   <name>OSCache</name>
+   <jiraId>10001</jiraId>
+   <jiraName>CACHE</jiraName>
+   <confluenceName>CACHE</confluenceName>
+   <svn>true</svn>
+
+    <forums>
+        <usersId>4</usersId>
+        <cvsId>12</cvsId>
+    </forums>
+
+    <evaluating>
+        <item>
+            <name>Overview</name>
+            <url>wiki/What%20is%20OSCache.html</url>
+        </item>
+        <item>
+            <name>Feature List</name>
+            <url>wiki/Feature%20List.html</url>
+        </item>
+        <item>
+            <name>Requirements</name>
+            <url>wiki/Requirements.html</url>
+        </item>
+        <item>
+            <name>Installation Guide</name>
+            <url>wiki/Installation%20Guide.html</url>
+        </item>
+        <item>
+            <name>Configuration Guide</name>
+            <url>wiki/Configuration.html</url>
+        </item>
+    </evaluating>
+
+    <docs>
+        <url>wiki/Documentation.html</url>
+        <items>
+            <item>
+                <name>FAQ</name>
+                <url>wiki/FAQ.html</url>
+            </item>
+            <item>
+                <name>Tag Reference</name>
+                <url>wiki/JSP%20Tags.html</url>
+            </item>
+            <item>
+                <name>The Caching Filter</name>
+                <url>wiki/CacheFilter.html</url>
+            </item>
+            <item>
+                <name>Cron Expressions</name>
+                <url>wiki/Cron%20Expressions.html</url>
+            </item>
+            <item>
+                <name>OSCache and Hibernate</name>
+                <url>wiki/Hibernate.html</url>
+            </item>
+            <item>
+                <name>Clustering</name>
+                <url>wiki/Clustering.html</url>
+            </item>
+        </items>
+    </docs>
+
+    <owners>
+       <person>
+           <name>Andres March</name>
+           <email>me at andresmarch dot com</email>
+       </person>
+   </owners>
+
+   <developers>
+	<person>
+           <name>Lars Torunski</name>
+           <email>ltorunski at t-online dot de</email>
+       </person>
+       <person>
+           <name>Chris Miller</name>
+           <email>chris_overseas at hotmail dot com</email>
+       </person>
+   </developers>
+
+   <releases>
+       <release>
+           <version>2.4.1</version>
+           <date>07/07/2007</date>
+           <jiraId>21705</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/61427/oscache-2.4.1-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.4</version>
+           <date>05/01/2007</date>
+           <jiraId>21660</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/57136/oscache-2.4-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.3.2</version>
+           <date>07/23/2006</date>
+           <jiraId>21651</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/37840/oscache-2.3.2-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.3.1</version>
+           <date>06/19/2006</date>
+           <jiraId>21630</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/36361/oscache-2.3.1-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.3</version>
+           <date>03/06/2006</date>
+           <jiraId>21570</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/30345/oscache-2.3-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.2 Final</version>
+           <date>11/06/2005</date>
+           <jiraId>21550</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/23623/oscache-2.2-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.2 RC</version>
+           <date>09/18/2005</date>
+           <jiraId>21450</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/20854/oscache-2.2-rc-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.1.1</version>
+           <date>5/01/2005</date>
+           <jiraId>21441</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/13962/oscache-2.1.1-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.1</version>
+           <date>1/18/2005</date>
+           <jiraId>21223</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/10654/oscache-2.1-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.0.2</version>
+           <date>1/22/2004</date>
+           <jiraId>21251</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/2653/oscache-2.0.2-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.0.1</version>
+           <date>1/04/2003</date>
+           <jiraId>21250</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/1580/oscache-2.0.1-full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       <release>
+           <version>2.0</version>
+           <date>09/22/2003</date>
+           <jiraId>21222</jiraId>
+           <downloads>
+               <download>
+                   <url>https://oscache.dev.java.net/files/documents/629/1051/oscache_2_0_full.zip</url>
+                   <name>Full</name>
+               </download>
+           </downloads>
+       </release>
+       
+   </releases>
+
+</project>
diff --git a/docs/navpanel.jsp b/docs/navpanel.jsp
new file mode 100644
index 0000000..7001dc9
--- /dev/null
+++ b/docs/navpanel.jsp
@@ -0,0 +1,41 @@
+<p>
+<h3>About</h3>
+<a href="<%= request.getContextPath() %>/oscache/wiki/What%20is%20OSCache.html">Overview</a> <br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Feature%20List.html">Feature List</a><br>
+<a href="http://oscache.dev.java.net/servlets/ProjectDocumentList">Download</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Change%20Log.html">Changelog</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Requirements.html">Requirements</a><br>
+</p>
+
+<p>
+<h3>Documentation</h3>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Installation%20Guide.html">Installation Guide</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/FAQ.html">FAQ</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Configuration.html">Configuration</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/JSP%20Tags.html">Tag Reference</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/CacheFilter.html">The Caching Filter</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Cron%20Expressions.html">Cron Expressions</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Hibernate.html">OSCache and Hibernate</a><br>
+<a href="<%= request.getContextPath() %>/oscache/wiki/Clustering.html">Clustering</a><br>
+<a href="<%= request.getContextPath() %>/oscache/api/">API Reference</a>
+</p>
+
+<p>
+<h3>Services</h3>
+<b>JIRA</b><br>
+- <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:openissues-panel">Issue Overview</a><br>
+- <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:roadmap-panel">Roadmap</a><br>
+- <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:changelog-panel">Changelog</a><br>
+<b>Wiki</b><br>
+- <a href="http://wiki.opensymphony.com/display/CACHE">OSCache page</a>
+</p>
+
+<p>
+
+<h3>Sponsor Companies</h3>
+These companies have contributed a lot to OSCache and we thank them.<br>
+
+<a href="http://www.pyxis-tech.com"><img src="<%= request.getContextPath() %>/oscache/pyxis-box-sponsor.gif" width="120" height="60" border="0" alt="Pyxis Technologies" vspace="2"></a><br>
+<a href="http://www.atlassian.com"><img src="<%= request.getContextPath() %>/oscache/atlassian-sponsor.gif" width="120" height="60" border="0" alt="Atlassian" vspace="2"></a>
+
+</p>
\ No newline at end of file
diff --git a/docs/wiki/7147/CacheChainModel_v3.jpg b/docs/wiki/7147/CacheChainModel_v3.jpg
new file mode 100644
index 0000000..03afc92
Binary files /dev/null and b/docs/wiki/7147/CacheChainModel_v3.jpg differ
diff --git a/docs/wiki/API Usage.html b/docs/wiki/API Usage.html
new file mode 100644
index 0000000..a25e42f
--- /dev/null
+++ b/docs/wiki/API Usage.html	
@@ -0,0 +1,76 @@
+<html>
+    <head>
+        <title>OSCache - 
+         Usage
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Beside the <a href="JSP Tags.html" title="JSP Tags">JSP tag library</a> and the <a href="CacheFilter.html" title="CacheFilter">CacheFilter</a> you can use OSCache through its straightforward API. You can use the <a href="http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/general/GeneralCacheAdministrator.html" title="Visit page outside Confluence">GeneralCacheAdministrator</a> to create, flush and administrate the cache. The GeneralCacheAdministrator has a cache  [...]
+
+<h4><a name="APIUsage-Typicalusewithfailover"></a>Typical use with fail over</h4>
+
+<div class="code"><div class="codeContent">
+<pre class="code-java"><span class="code-object">String</span> myKey = <span class="code-quote">"myKey"</span>;
+<span class="code-object">String</span> myValue;
+<span class="code-object">int</span> myRefreshPeriod = 1000;
+<span class="code-keyword">try</span> {
+    <span class="code-comment">// Get from the cache
+</span>    myValue = (<span class="code-object">String</span>) admin.getFromCache(myKey, myRefreshPeriod);
+} <span class="code-keyword">catch</span> (NeedsRefreshException nre) {
+    <span class="code-keyword">try</span> {
+        <span class="code-comment">// Get the value (probably from the database)
+</span>        myValue = <span class="code-quote">"This is the content retrieved."</span>;
+        <span class="code-comment">// Store in the cache
+</span>        admin.putInCache(myKey, myValue);
+    } <span class="code-keyword">catch</span> (Exception ex) {
+        <span class="code-comment">// We have the current content <span class="code-keyword">if</span> we want fail-over.
+</span>        myValue = (<span class="code-object">String</span>) nre.getCacheContent();
+        <span class="code-comment">// It is essential that cancelUpdate is called <span class="code-keyword">if</span> the
+</span>        <span class="code-comment">// cached content is not rebuilt
+</span>        admin.cancelUpdate(myKey);
+    }
+}</pre>
+</div></div>
+
+<h4><a name="APIUsage-Typicalusewithoutfailover"></a>Typical use without fail over</h4>
+
+<div class="code"><div class="codeContent">
+<pre class="code-java"><span class="code-object">String</span> myKey = <span class="code-quote">"myKey"</span>;
+<span class="code-object">String</span> myValue;
+<span class="code-object">int</span> myRefreshPeriod = 1000;
+<span class="code-keyword">try</span> {
+    <span class="code-comment">// Get from the cache
+</span>    myValue = (<span class="code-object">String</span>) admin.getFromCache(myKey, myRefreshPeriod);
+} <span class="code-keyword">catch</span> (NeedsRefreshException nre) {
+    <span class="code-keyword">try</span> {
+        <span class="code-comment">// Get the value (probably from the database)
+</span>        myValue = <span class="code-quote">"This is the content retrieved."</span>;
+        <span class="code-comment">// Store in the cache
+</span>        admin.putInCache(myKey, myValue);
+        updated = <span class="code-keyword">true</span>;
+    } <span class="code-keyword">finally</span> {
+        <span class="code-keyword">if</span> (!updated) {
+            <span class="code-comment">// It is essential that cancelUpdate is called <span class="code-keyword">if</span> the
+</span>            <span class="code-comment">// cached content could not be rebuilt
+</span>            admin.cancelUpdate(myKey);
+        }
+    }
+}</pre>
+</div></div>
+
+<h4><a name="APIUsage-Note"></a>Note</h4>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Be Careful</b><br />
+<p>If a NeedsRefreshException is raised you have to invoke admin.putInCache or even admin.cancelUpdate to avoid deadlock situation.</p></td></tr></table>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/CacheFilter Tutorial.html b/docs/wiki/CacheFilter Tutorial.html
new file mode 100644
index 0000000..779950b
--- /dev/null
+++ b/docs/wiki/CacheFilter Tutorial.html	
@@ -0,0 +1,180 @@
+<html>
+    <head>
+        <title>OSCache - 
+        CacheFilter Tutorial
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="CacheFilterTutorial-Introduction"></a>Introduction </h3>
+
+<p><b>OSCache</b> comes with a servlet filter that enables you to transparently cache entire pages of your website, and even binary files. Caching of binary files is extremely useful when they are generated dynamically, e.g. PDF files or images. In addition by using the last modified header the transaction overhead and server load is reduced excellently which speed ups the server response time.</p>
+
+<p>How to configure OSCache to cache entire servlet responses is described in the <a href="CacheFilter.html" title="CacheFilter">configuration page of the CacheFilter</a>. This short tutorial should demonstrate how to make your web site more responsive, and save load on your server. Using the CacheFilter the user will appreciate a faster loading site and will visit it more often.</p>
+
+<h3><a name="CacheFilterTutorial-Improvements"></a>Improvements</h3>
+
+<p>Major improvements have been made to the CacheFilter in the releases 2.2 and 2.3:</p>
+
+<ul>
+	<li>Default initialization of the last modified header which reduces transaction overhead and server load</li>
+	<li>CRON expressions to expire content at specific dates and/or times</li>
+	<li>Preserving more http headers, e.g. the expires header</li>
+	<li>Special handling for fragments of a page</li>
+	<li>Custom cache key generation by subclassing CacheFilter or by implementing a special interface</li>
+	<li>Custom cache groups generation by subclassing CacheFilter or by implementing a special interface</li>
+	<li>Support of GZip filters in the filter chain</li>
+	<li>Avoids session creation for application scope pages</li>
+	<li>Reduced memory consumption</li>
+	<li>Multiple matching cache filters won't dead-lock the response anymore</li>
+	<li>The cache won't be serve the same response twice before the client begins to cache it anymore</li>
+</ul>
+
+
+<h3><a name="CacheFilterTutorial-CacheableContent"></a>Cacheable Content</h3>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Cacheable content</b><br />
+<p>Note that the filter will only cache content that has a status of 200 (HttpServletResponse.SC_OK).</p></td></tr></table>
+
+<h3><a name="CacheFilterTutorial-Configuringthefilter"></a>Configuring the filter</h3>
+
+<h4><a name="CacheFilterTutorial-Example1"></a>Example 1</h4>
+
+<p>To configure the filter, add something like the following to your <tt>web.xml</tt> file (obviously you will want to set the URL pattern to match only the content you want to cache; this example will cache all JSP pages for 10 minutes in session scope):</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><filter></span>
+    <span class="code-tag"><filter-name></span>CacheFilter<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><filter-class></span>com.opensymphony.oscache.web.filter.CacheFilter<span class="code-tag"></filter-class></span>
+    <span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>time<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>600<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span>
+    <span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>scope<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>session<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span>
+<span class="code-tag"></filter></span>
+
+<span class="code-tag"><filter-mapping></span>
+    <span class="code-tag"><filter-name></span>CacheFilter<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><url-pattern></span>*.jsp<span class="code-tag"></url-pattern></span>
+<span class="code-tag"></filter-mapping></span></pre>
+</div></div>
+
+<p>The default duration is one hour and the default scope for the cache is application scope. You can change these settings using <a href="CacheFilter.html" title="CacheFilter">initialization parameters</a>.</p>
+
+<h4><a name="CacheFilterTutorial-Example2"></a>Example 2</h4>
+
+<p>The initialization of the last modified header based on the current time reduces transaction overhead and server load, because the browser can ask the server if the cached content in the browser cache was changed on the server since the last request. If the content wasn't changed , the server will response with the status 304 (not modified).</p>
+
+<p>Furthermore if the <a href="CacheFilter.html#CacheFilter-expires" title="expires on CacheFilter">expires parameter</a> is the set to <em>time</em>, the server will send the date and time after which the content is considered stale. Then common browsers won't request the server anymore until the cached content is considered stale. The example will cache the content for one hour by default and the expires date and time will be calculated based on the creation time and the <em>time</em>  [...]
+
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><filter></span>
+    <span class="code-tag"><filter-name></span>CacheFilterStaticContent<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><filter-class></span>com.opensymphony.oscache.web.filter.CacheFilter<span class="code-tag"></filter-class></span>
+    <span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>expires<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>time<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span>
+<span class="code-tag"></filter></span>
+
+<span class="code-tag"><filter-mapping></span>
+    <span class="code-tag"><filter-name></span>CacheFilterStaticContent<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><url-pattern></span>*.jsp<span class="code-tag"></url-pattern></span>
+<span class="code-tag"></filter-mapping></span></pre>
+</div></div>
+
+<h3><a name="CacheFilterTutorial-Usingthefilter"></a>Using the filter</h3>
+
+<h4><a name="CacheFilterTutorial-Example1%3AICacheKeyProvider"></a>Example 1: ICacheKeyProvider</h4>
+
+<p>A simple example how to use the <a href="CacheFilter.html#CacheFilter-ICacheKeyProvider" title="ICacheKeyProvider on CacheFilter">ICacheKeyProvider</a> parameter of the CacheFilter. The cache key in constructed with the http request URI and with two request parameters <em>pageid</em> and <em>pagination</em>.</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> javax.servlet.http.HttpServletRequest;
+
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Cache;
+<span class="code-keyword">import</span> com.opensymphony.oscache.web.ServletCacheAdministrator;
+<span class="code-keyword">import</span> com.opensymphony.oscache.web.filter.ICacheKeyProvider;
+
+<span class="code-keyword">public</span> class ExampleCacheKeyProvider <span class="code-keyword">implements</span> ICacheKeyProvider {
+
+    <span class="code-keyword">public</span> <span class="code-object">String</span> createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
+
+        <span class="code-comment">// buffer <span class="code-keyword">for</span> the cache key
+</span>        <span class="code-object">StringBuffer</span> buffer = <span class="code-keyword">new</span> <span class="code-object">StringBuffer</span>(100);
+        
+        <span class="code-comment">// part 1 of the key: the request uri
+</span>        buffer.append(httpRequest.getRequestURI());
+        
+        <span class="code-comment">// separation
+</span>        buffer.append('_');
+
+        <span class="code-comment">// part 2 of the key: the page id
+</span>        buffer.append(httpRequest.getParameter(<span class="code-quote">"pageid"</span>));
+        
+        <span class="code-comment">// separation
+</span>        buffer.append('_');
+        
+        <span class="code-comment">// part 3 of the key: the pagination
+</span>        buffer.append(httpRequest.getParameter(<span class="code-quote">"pagination"</span>));
+        
+        <span class="code-keyword">return</span> buffer.toString();
+    }
+
+}</pre>
+</div></div>
+
+<p>You can use session attributes values for the cache key also, if request parameters aren't available or e.g. security settings have to be add to the cache key.</p>
+
+<h4><a name="CacheFilterTutorial-Example2%3AFlush"></a>Example 2: Flush</h4>
+
+<p>The flush example shows how to flush a CacheFilter with scope <em>application</em> based on group names. In this example the http servlet request of the user is required to get the cache object.</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> com.opensymphony.oscache.base.Cache;
+<span class="code-keyword">import</span> com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+<span class="code-keyword">import</span> java.util.Collection;
+<span class="code-keyword">import</span> java.util.Iterator;
+
+<span class="code-keyword">import</span> javax.servlet.http.HttpServletRequest;
+<span class="code-keyword">import</span> javax.servlet.jsp.PageContext;
+
+<span class="code-keyword">public</span> class OSCacheAdmin {
+    
+    /**
+     * flush the CacheFilter according to dependent group
+     *
+     * @param request the HttpServletRequest of the user
+     * @param groupNames a string collection of group names
+     */
+    <span class="code-keyword">public</span> <span class="code-keyword">static</span> void flushCacheGroup(HttpServletRequest request, Collection groupNames) {
+    	Cache cache = ServletCacheAdministrator.getInstance(request.getSession().getServletContext()).getCache(request, PageContext.APPLICATION_SCOPE); 
+        Iterator groups = groupNames.iterator();
+        <span class="code-keyword">while</span> (groups.hasNext()) {
+            <span class="code-object">String</span> group = (<span class="code-object">String</span>) groups.next();
+            cache.flushGroup(group);
+        }
+    }
+}</pre>
+</div></div>
+
+<p>If you're CacheFilter is running with scope <em>session</em>, you have to get the cache as follows:</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-java">Cache cache = ServletCacheAdministrator.getInstance(request.getSession(<span class="code-keyword">true</span>).getServletContext()).getCache(request, PageContext.SESSION_SCOPE);</pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/CacheFilter.html b/docs/wiki/CacheFilter.html
new file mode 100644
index 0000000..6d2dde7
--- /dev/null
+++ b/docs/wiki/CacheFilter.html
@@ -0,0 +1,118 @@
+<html>
+    <head>
+        <title>OSCache - 
+        CacheFilter
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p><b>OSCache</b> comes with a servlet filter that enables you to transparently cache entire pages of your website, and even binary files. Caching of binary files is extremely useful when they are generated dynamically, e.g. PDF files or images.</p>
+
+<p>A <a href="CacheFilter Tutorial.html" title="CacheFilter Tutorial">tutorial</a> describes how to cache entire pages of your website and what performance improvements can be done with the CacheFilter.</p>
+
+<p>Beginning with release 2.4 you are be able to set/override the CacheFilter initialization parameters at runtime.</p>
+
+<h4><a name="CacheFilter-CacheableContent"></a>Cacheable Content</h4>
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Cacheable content</b><br /><br/>
+Note that the filter will only cache content that has a status of 200 (HttpServletResponse.SC_OK).</td></tr></table>
+
+<h3><a name="CacheFilter-Configuringthefilter"></a>Configuring the filter</h3>
+
+<p>To configure the filter, use the <tt>oscache.properties</tt> to <a href="Configuration.html" title="Configuration">configure</a> the core settings of OSCache and add something like the following to your <tt>web.xml</tt> file:</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><filter></span>
+    <span class="code-tag"><filter-name></span>CacheFilter<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><filter-class></span>com.opensymphony.oscache.web.filter.CacheFilter<span class="code-tag"></filter-class></span>
+    <span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>time<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>600<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span>
+    <span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>scope<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>session<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span>
+<span class="code-tag"></filter></span>
+
+<span class="code-tag"><filter-mapping></span>
+    <span class="code-tag"><filter-name></span>CacheFilter<span class="code-tag"></filter-name></span>
+    <span class="code-tag"><url-pattern></span>*.jsp<span class="code-tag"></url-pattern></span>
+<span class="code-tag"></filter-mapping></span></pre>
+</div></div>
+<p>Obviously you will want to set the URL pattern to match only the content you want to cache; this example will cache all JSP pages for 10 minutes in session scope. The default duration is one hour and the default scope for the cache is application scope. </p>
+
+<p>If the <a href="#CacheFilter-ICacheKeyProvider" title="ICacheKeyProvider on CacheFilter">ICacheKeyProvider</a> parameter isn't set, the CacheFilter will use the HTTP request URI and the QueryString to create the cache key. </p>
+
+<p>You can change the CacheFilter settings using the following initialization parameters.</p>
+
+<h4><a name="CacheFilter-Parameter%3Atime"></a><a name="CacheFilter-time"></a>Parameter: time</h4>
+
+<p>The time parameter sets the cache time (in seconds) for the content. The default cache time is one hour.</p>
+
+<p>Specifying <em>-1</em> (indefinite expiry) as the cache time will ensure a content does not become stale until it is either explicitly flushed or the expires refresh policy causes the entry to expire.</p>
+
+<h4><a name="CacheFilter-Parameter%3Ascope"></a><a name="CacheFilter-scope"></a>Parameter: scope</h4>
+
+<p>The scope parameter lets you set the scope to cache content in. Valid values for the scope are <em>application</em> (default) and <em>session</em>.</p>
+
+<h4><a name="CacheFilter-Parameter%3Acron%28NEW%5C%21Since2.3%29"></a><a name="CacheFilter-cron"></a>Parameter: cron (NEW! Since 2.3)</h4>
+
+<p>A cron expression that determines when the page content will expire. This allows content to be expired at particular dates and/or times, rather than once a cache entry reaches a certain age. See <a href="Cron Expressions.html" title="Cron Expressions">Cron Expressions</a> to read more about this attribute. Please consider that the (default) time value is still evaluated, hence the time value should be set to indefinite expiry.</p>
+
+<h4><a name="CacheFilter-Parameter%3Afragment%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-fragment"></a>Parameter: fragment (NEW! Since 2.2)</h4>
+
+<p>Defines if the filter handles fragments of a page. Acceptable values are <em>auto</em> for auto detect, <em>no</em> for false and <em>yes</em> for true. The default value is auto detect which checks the <em>javax.servlet.include.request_uri</em> request attribute. Fragments of a page shouldn't be gzipped or evaluate the last modified header.</p>
+
+<h4><a name="CacheFilter-Parameter%3Anocache%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-nocache"></a>Parameter: nocache (NEW! Since 2.2)</h4>
+
+<p>Defines which objects shouldn't be cached. Acceptable values are <em>off</em> (default) for caching all objects and <em>sessionIdInURL</em> for don't cache page if the session id is contained in the URL.</p>
+
+<h4><a name="CacheFilter-Parameter%3AlastModified%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-lastModified"></a>Parameter: lastModified (NEW! Since 2.2)</h4>
+
+<p>Defines if the last modified header will be sent in the response. Acceptable values are <em>off</em> for don't sending the header, even it is set in the filter chain, <em>on</em> for sending it if it is set in the filter chain and <em>initial</em> (default) the last modified information will be set based on current time.</p>
+
+<h4><a name="CacheFilter-Parameter%3Amaxage%28NEW%5C%21Since2.3.1%29"></a><a name="CacheFilter-maxage"></a>Parameter: max-age (NEW! Since 2.3.1)</h4>
+
+<p>Specifies the maximum amount of time in seconds that the cache content will be considered new in the browser's cache. The browser will retrieve the content from it's own cache for the amount of time without requesting the web server again. The default max-age time is 60 seconds. Combined with the <a href="#CacheFilter-lastModified" title="lastModified on CacheFilter">last modified header</a> the transaction overhead and server load is reduced excellently which speed ups the server res [...]
+
+<h4><a name="CacheFilter-Parameter%3Aexpires%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-expires"></a>Parameter: expires (NEW! Since 2.2)</h4>
+
+<p>Defines if the expires header will be sent in the response. Acceptable values are <em>off</em> for don't sending the header, even it is set in the filter chain, <em>on</em> (default) for sending it if it is set in the filter chain and <em>time</em> the expires information will be intialized based on the time parameter and creation time of the content.<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgro [...]
+The parameter <em>time</em> would force the CacheFilter to send the expires header, because the value is set always. The developer must consider that some browsers evaluate the value and will use the cached content in the browsers cache, until the content is expired. Consequently a flush of the cache in the web application won't update a page in the browser cache. Hence different users may see see a different status of page.</td></tr></table></p>
+
+<h4><a name="CacheFilter-Parameter%3AICacheKeyProvider%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-ICacheKeyProvider"></a>Parameter: ICacheKeyProvider (NEW! Since 2.2)</h4>
+
+<p>Specify a class which implements the interface <tt>ICacheKeyProvider</tt>. A developer can implement a class which provides cache keys based on the request, the servlect cache administrator and the cache.</p>
+
+<h4><a name="CacheFilter-Parameter%3AICacheGroupsProvider%28NEW%5C%21Since2.2%29"></a><a name="CacheFilter-ICacheGroupsProvider"></a>Parameter: ICacheGroupsProvider (NEW! Since 2.2)</h4>
+
+<p>Specify a class which implements the interface <tt>ICacheGroupsProvider</tt>. A developer can implement a class which provides cache groups based on the request, the servlect cache administrator and the cache.</p>
+
+<h4><a name="CacheFilter-Parameter%3AEntryRefreshPolicy%28New%5C%21Since2.3%29"></a><a name="CacheFilter-EntryRefreshPolicy"></a>Parameter: EntryRefreshPolicy (New! Since 2.3)</h4>
+
+<p>Specify a class which implements the interface <tt>EntryRefreshPolicy</tt>. A developer can implement a class which provides a different custom cache invalidation policy for a specific cache entry. If not specified, the default policy is timed entry expiry as specified with the <em>time</em> parameter described above. </p>
+
+<h4><a name="CacheFilter-Parameter%3AdisableCacheOnMethods%28New%5C%21Since2.4%29"></a><a name="CacheFilter-disableCacheOnMethods"></a>Parameter: disableCacheOnMethods (New! Since 2.4)</h4>
+
+<p>Specify HTTP method names in a comma separated list for which cacheing should be disabled. The default value is <code>null</code> for cacheing all requests without regarding the method name. See <a href="http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod()" title="Visit page outside Confluence">HttpServletRequest#getMethod</a>, e.g.:</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><init-param></span>
+        <span class="code-tag"><param-name></span>disableCacheOnMethods<span class="code-tag"></param-name></span>
+        <span class="code-tag"><param-value></span>POST,PUT,DELETE<span class="code-tag"></param-value></span>
+    <span class="code-tag"></init-param></span></pre>
+</div></div>
+
+<h4><a name="CacheFilter-Parameter%3Aoscachepropertiesfile%28New%5C%21Since2.4%29"></a><a name="CacheFilter-oscachepropertiesfile"></a>Parameter: oscache-properties-file (New! Since 2.4)</h4>
+<p>By specifying a OSCache properties file for a CacheFilter, the developer can run multiple caches each with different <a href="Configuration.html" title="Configuration">configurations</a> tailored to the requirements of the application. In each properties file the developer has to define a unique <a href="Configuration.html#Configuration-cache.key" title="cache.key on Configuration">cache.key</a> otherwise the default properties file is used. If the parameter is not specified, the defa [...]
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Chain Caching Model.html b/docs/wiki/Chain Caching Model.html
new file mode 100644
index 0000000..149dac0
--- /dev/null
+++ b/docs/wiki/Chain Caching Model.html	
@@ -0,0 +1,127 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Chain Caching Model
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h2><a name="ChainCachingModel-Discussion"></a>Discussion</h2>
+
+<p><ins>Lars wrote:</ins><br/>
+Would it be possible to use the intercepting filter pattern to support all possible cache models with a lot of combination capabilities? It's possible to define the capacity for each cache etc.</p>
+
+<p>DiskPersistence, SoftReferenceCache etc. would implement the Command interface of Commons Chain or a new interface of OSCache.</p>
+
+<p><a href="http://jakarta.apache.org/commons/chain/" title="Visit page outside Confluence">http://jakarta.apache.org/commons/chain/</a><br/>
+<a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html" title="Visit page outside Confluence">http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html</a></p>
+
+<p><ins>Andres wrote:</ins><br/>
+I think that is a good idea but it may be overkill for the most use cases.  Also, not every cache interceptor could have all the capabilities.  In particular, how would you imagine a realistic use case for a cache.get() call.  Should multiple interceptors return values?</p>
+
+<p>I think disk persistence is still in, although I don't think it will be like it is now.  We will be accepting Object keys, so any cache impl will need to accept them.  I have been thinking about a lightweight object db that has persistence built-in but I'm not sure.</p>
+
+<p>This is definitely an interesting topic and I'd like to discuss it more.<br/>
+Now is the time to make these sort of decisions.</p>
+
+<p><ins>Lars wrote:</ins><br/>
+The cache interceptors are cascaded. If the first interceptor doesn't return a value then the 2nd interceptor will be requested.</p>
+
+<p><ins>Andres wrote:</ins><br/>
+The scenarios below basically describe a more flexible implementation of the 2 cache (memory and disk) architecture in place.  There is no doubt that a chained design can be better.  I would be mindful of the abuse that can occur with a inifinitely flexible system.  I prefer though to allow people to shoot themselves in the foot but make it extremely easy not to do so by providing very simple out of the box implementations.  I have attached a <a href="http://wiki.open [...]
+
+<p><ins>Lars wrote:</ins><br/>
+The interface needed for the Cache Link (which you describe in a <a href="http://wiki.opensymphony.com/download/attachments/7147/cache+sequences.pdf?version=1" title="cache sequences.pdf attached to Chain Caching Model">simple sequence diagram</a>) is exactly the same interface as the EvictionAlgorithm. The Cache Chain has to handle in which Cache Link a cache content should be. Furthermore the Cache Chain has to put an evicted cache content from the memory cache to the disk cache.</p>
+
+<p>1.) How do you want to synchronize the access to the same cache content? In OSCache 2 this is done by the EntryUpdateState based on the key.<br/>
+2.) Should the Cache Chain contain all the cache keys without knowing in which Cache Link the content is? Or should each Cache Link contain it's own cache keys?</p>
+
+<p>Some other points:<br/>
+3.) Please update the java doc of EvictionAlgorithm, because some parameters are wrong.<br/>
+4.) currently you synchronize the cache events, I think there maybe a performance loss, because in my environments a lot of events are fired</p>
+
+<p>I added a <a href="http://wiki.opensymphony.com/download/attachments/7147/CacheChainModel_v1.pdf?version=1" title="CacheChainModel_v1.pdf attached to Chain Caching Model">simple class diagramm</a> and saved the diagramm in the <a href="http://wiki.opensymphony.com/download/attachments/7147/oscache.fpr.gz?version=4" title="oscache.fpr.gz attached to Chain Caching Model">Fujaba format</a>.</p>
+
+<p><ins>Andres wrote:</ins></p>
+
+<p>The cache chain should have no knowledge of what is in any of the cache links.  However, the issue of eviction is clear.  When a put() is called, the link should return an evicted entry or null if the cache is not full.  The chain will then know if it needs to continue the put into the next link.  To clarify the interfaces, I think a Chain interface should extend Map.  The Link and EvictionAlgorithm should themselves be interfaces.  Link could have implementations such as memory, disk [...]
+
+<p>1.) In my branch, I have synchronized the entire cache on each cache access.  I think this will still be fast enough and will surely be more stable.  basically, get, put, and remove are sync'd.  I do not think we need to achieve a highly concurrent cache in order to provide a solution that is hundreds of times faster than db or disk access.</p>
+
+<p>However, we could add functionality the improves performance but does not cause deadlocks, such as a write behind feature on puts, so that puts get queued and another thread does the work when it has time. </p>
+
+<p>2.) I don't think this would be wise.  I don't think the chain should have knowledge of the keys.  I think all it should have is references to the links and stateless logic.  Either way each link would need to keep its own keys, therefore putting them in the chain would add another map that would have to be accessed and slow performance.</p>
+
+<p>There are 3 places I believe the keys must exist: in the store (duh), in the algorithm (or we could generalize this as any metrics collector), and in the groups map.</p>
+
+<p>Group functionality is a similar issue.  I had wanted to drop this functionality but it seems the people that use cache tags (I never have yet) really depend on them.  This functionality is unique to OSCache as far as I am aware.<br/>
+Maintaining the groups in each link could kill performance.  I think we need a GroupManager that exists outside of the cache links and is referenced from the chain.  This way it is only called once per chain.  One disadvantage would be that when a group is removed each remove would have to be called on each link until it found the correct store. </p>
+
+<p>3./4.) Yeah, that is sort of borrowed code and is not necessary.  However, we need to be mindful of the access to the listener list.  The easiest way is to probably make the list implementation a SynchronizedArrayList or something.</p>
+
+<p>The way I am thinking the current code in my branch could be moved over to a chain model is:</p>
+<ol>
+	<li>most of the BaseCache code gets put into the chain minus the algorithm and group map code.</li>
+	<li>the MemoryCache gets turned into one of the link implementations and gets the algorithm reference</li>
+	<li>the group map code gets refactored into a GroupManager and called from the chain.</li>
+</ol>
+
+
+<p><ins>Lars wrote:</ins><br/>
+I think the implementations of the CacheChain interface could be a SimplePipeCacheChain and a SizeBasedCacheChain. The SimplePipeCacheChain is comparable to the current architectur. The SizeBasesCacheChain puts the cache objects to the different CacheLinks based on the cache content sizes, e.g. large images a stored to disk and not in memory.</p>
+
+<p><img src="Chain Caching Model_attachments/CacheChainModel_v3.jpg" align="absmiddle" border="0" /></p>
+
+<p>The default CacheChain should be the SimplePipeCacheChain. The SizeBasedCacheChain can be implemented as part of a 3.1 release.</p>
+
+<h2><a name="ChainCachingModel-Scenariostobecheckedandtested"></a>Scenarios to be checked and tested</h2>
+
+<p>Configuration with a LRU algorithm: (1) MemoryCache -<del>> (2) SoftRefCache -</del>> (3) DiskPersistCache</p>
+
+<h3><a name="ChainCachingModel-ScenarioA%3AGetforaobjectinSoftRefCache"></a>Scenario A: Get for a object in SoftRefCache</h3>
+
+<ol>
+	<li>the cache object x1 is in the SoftRefCache</li>
+	<li>the 1st getEntry will return null for the MemoryCache</li>
+	<li>the 2nd getEntry will find the cache object x1 in SoftRefCache</li>
+	<li>cache object x1 has to be removed from the SoftRefCache and has to put into the 1st cache (or maybe in the previous cache <- design decission).</li>
+	<li>the cache object x1 will edge out the cache object xi (LRU) from the MemoryCache and the cache object xi has to me removed from the MemoryCache and to put in the next cache (SoftRefCache).</li>
+</ol>
+
+
+<h3><a name="ChainCachingModel-ScenarioB%3AGetforaobjectinDiskPersistCache"></a>Scenario B: Get for a object in DiskPersistCache</h3>
+
+<ol>
+	<li>the cache object x2 is in the DiskPersistCache</li>
+	<li>the 1st getEntry will return null for the MemoryCache</li>
+	<li>the 2nd getEntry will return null for the SoftRefCache</li>
+	<li>the 3rd getEntry will find the cache object x2 in DiskPersistCache</li>
+	<li>cache object x2 has to be removed from the DiskPersistCache and has to put into the 1st cache. The cache object x2 will edge out xj in MemoryCache, which has to be put in SoftRefCache. Hence xj will edge out xk in SoftRefCache, which has to be put in DiskPersistCache</li>
+</ol>
+
+
+<h3><a name="ChainCachingModel-ScenarioC%3APutanewobject"></a>Scenario C: Put a new object</h3>
+
+<ol>
+	<li>the new cache object x3 should be put into the cache</li>
+	<li>the cache object x3 will be edge out a cache object xa from MemoryCache</li>
+	<li>xa has to be put into SoftRefCache, where xa will edge out xb</li>
+	<li>xb has to be put in DiskPersistCache, where xb will edge out xc</li>
+	<li>until DiskPersistCache is not unlimited the xc cache object has to be removed from cache. Hence the cache key for xc has to be removed from the map.</li>
+</ol>
+
+
+<h3><a name="ChainCachingModel-ScenarioD%3APutastaleobjectorgetastaleobject"></a>Scenario D: Put a stale object or get a stale object</h3>
+
+<p><b>TODO</b></p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v1.pdf b/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v1.pdf
new file mode 100644
index 0000000..3e2d2a3
Binary files /dev/null and b/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v1.pdf differ
diff --git a/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v3.jpg b/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v3.jpg
new file mode 100644
index 0000000..03afc92
Binary files /dev/null and b/docs/wiki/Chain Caching Model_attachments/CacheChainModel_v3.jpg differ
diff --git a/docs/wiki/Chain Caching Model_attachments/cache sequences.pdf b/docs/wiki/Chain Caching Model_attachments/cache sequences.pdf
new file mode 100644
index 0000000..04fabda
Binary files /dev/null and b/docs/wiki/Chain Caching Model_attachments/cache sequences.pdf differ
diff --git a/docs/wiki/Chain Caching Model_attachments/oscache.fpr.gz b/docs/wiki/Chain Caching Model_attachments/oscache.fpr.gz
new file mode 100644
index 0000000..422b876
Binary files /dev/null and b/docs/wiki/Chain Caching Model_attachments/oscache.fpr.gz differ
diff --git a/docs/wiki/Change Log.html b/docs/wiki/Change Log.html
new file mode 100644
index 0000000..2f1835a
--- /dev/null
+++ b/docs/wiki/Change Log.html	
@@ -0,0 +1,122 @@
+<html>
+<head>
+<title>OSCache - Change Log</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<h3><a name="ChangeLog-ReleaseNotes"></a>Release Notes</h3>
+		<ul>
+			<li>2007
+			<ul>
+				<li><a href="OSCache 2.4.1.html" title="OSCache 2.4.1">OSCache
+				2.4.1</a></li>
+				<li><a href="OSCache 2.4.html" title="OSCache 2.4">OSCache
+				2.4</a></li>
+			</ul>
+			</li>
+			<li>2006
+			<ul>
+				<li><a href="OSCache 2.3.2.html" title="OSCache 2.3.2">OSCache
+				2.3.2</a></li>
+				<li><a href="OSCache 2.3.1.html" title="OSCache 2.3.1">OSCache
+				2.3.1</a></li>
+				<li><a href="OSCache 2.3.html" title="OSCache 2.3">OSCache
+				2.3</a></li>
+			</ul>
+			</li>
+			<li>2005
+			<ul>
+				<li><a href="OSCache 2.2.html" title="OSCache 2.2">OSCache
+				2.2</a></li>
+				<li><a href="OSCache 2.2 RC.html" title="OSCache 2.2 RC">OSCache
+				2.2 RC</a></li>
+				<li><a href="OSCache 2.1.1.html" title="OSCache 2.1.1">OSCache
+				2.1.1</a></li>
+				<li><a href="OSCache 2.1.html" title="OSCache 2.1">OSCache
+				2.1</a></li>
+			</ul>
+			</li>
+			<li>2004
+			<ul>
+				<li><a href="OSCache 2.0.2.html" title="OSCache 2.0.2">OSCache
+				2.0.2</a></li>
+			</ul>
+			</li>
+			<li>2003
+			<ul>
+				<li><a href="OSCache 2.0.1.html" title="OSCache 2.0.1">OSCache
+				2.0.1</a></li>
+				<li><a href="OSCache 2.0.html" title="OSCache 2.0">OSCache
+				2.0</a></li>
+				<li><a href="OSCache 2.0 beta 2.html"
+					title="OSCache 2.0 beta 2">OSCache 2.0 beta 2</a></li>
+				<li><a href="OSCache 2.0 beta 1.html"
+					title="OSCache 2.0 beta 1">OSCache 2.0 beta 1</a></li>
+			</ul>
+			</li>
+			<li>2002
+			<ul>
+				<li><a href="OSCache 1.7.5.html" title="OSCache 1.7.5">OSCache
+				1.7.5</a></li>
+			</ul>
+			</li>
+			<li>2001
+			<ul>
+				<li><a href="OSCache 1.7.4.html" title="OSCache 1.7.4">OSCache
+				1.7.4</a></li>
+				<li><a href="OSCache 1.7.3.html" title="OSCache 1.7.3">OSCache
+				1.7.3</a></li>
+				<li><a href="OSCache 1.7.2.html" title="OSCache 1.7.2">OSCache
+				1.7.2</a></li>
+				<li><a href="OSCache 1.7.1.html" title="OSCache 1.7.1">OSCache
+				1.7.1</a></li>
+				<li><a href="OSCache 1.7.0.html" title="OSCache 1.7.0">OSCache
+				1.7.0</a></li>
+				<li><a href="OSCache 1.6.1.html" title="OSCache 1.6.1">OSCache
+				1.6.1</a></li>
+				<li><a href="OSCache 1.6.html" title="OSCache 1.6">OSCache
+				1.6</a></li>
+				<li><a href="OSCache 1.5.html" title="OSCache 1.5">OSCache
+				1.5</a></li>
+				<li><a href="OSCache 1.3.html" title="OSCache 1.3">OSCache
+				1.3</a></li>
+				<li><a href="OSCache 1.2.5.html" title="OSCache 1.2.5">OSCache
+				1.2.5</a></li>
+				<li><a href="OSCache 1.2.1.html" title="OSCache 1.2.1">OSCache
+				1.2.1</a></li>
+				<li><a href="OSCache 1.2.html" title="OSCache 1.2">OSCache
+				1.2</a></li>
+				<li><a href="OSCache 1.1.html" title="OSCache 1.1">OSCache
+				1.1</a></li>
+				<li><a href="OSCache 1.0 beta 2.html"
+					title="OSCache 1.0 beta 2">OSCache 1.0 beta 2</a></li>
+				<li><a href="OSCache 1.0 beta 1.html"
+					title="OSCache 1.0 beta 1">OSCache 1.0 beta 1</a></li>
+			</ul>
+			</li>
+			<li>2000
+			<ul>
+				<li><a href="OSCache 1.0 beta 0.html"
+					title="OSCache 1.0 beta 0">OSCache 1.0 beta 0</a></li>
+			</ul>
+			</li>
+		</ul>
+
+
+		<p>See also <a
+			href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:changelog-panel"
+			title="Visit page outside Confluence">JIRA - Change Log</a> or read
+		the complete <a href="Complete Change Log.html"
+			title="Complete Change Log">release notes at once</a>.</p>
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/Clustering.html b/docs/wiki/Clustering.html
new file mode 100644
index 0000000..96406bb
--- /dev/null
+++ b/docs/wiki/Clustering.html
@@ -0,0 +1,79 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Clustering
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>New in OSCache 2.0 is support for clustering of caches. OSCache currently ships with implementations that allow you to use either JavaGroups or JMS as the underlying broadcast protocol.</p>
+
+<p>Caches across a cluster only broadcast messages when flush events occur. This means that the content of the caches are built up independently on each server, but whenever content becomes stale on one server it is made stale on them all. This provides a very high performing solution since we never have to pass cached objects around the cluster. And since there is no central server that is in charge of the cluster, the clustering is very robust.</p>
+
+<p>Configuring OSCache to cluster is very simple. Follow either the JMS or the JavaGroups instructions below depending on which protocol you want to use.</p>
+
+<h3><a name="Clustering-JMSConfiguration"></a>JMS Configuration</h3>
+
+<p>Configure your JMS server. OSCache requires that a JMS ConnectionFactory and a Topic are available via JNDI. See your JMS server's documentation for details.</p>
+
+<p>Add the JMS broadcasting listener to your oscache.properties file like this:</p>
+
+<div class="preformatted"><div class="preformattedContent">
+<pre>cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroadcastingListener
+</pre>
+</div></div>
+
+<p>(Note that this listener requires JMS 1.1 or higher, however legacy support for 1.0.x is also provided. If your JMS server only supports JMS 1.0.x then use <tt>JMS10BroadcastingListener</tt> instead of <tt>JMSBroadcastingListener</tt>. The rest of this documentation applies equally to both the 1.1 and 1.0 listeners.)</p>
+
+<p>The JMS listener supports the following configuration parameters:</p>
+
+<ul>
+	<li><b>cache.cluster.jms.topic.factory</b> - The JNDI name that binds the JMS topic connection factory. This should match the name that is specified in your JMS server's configuration. Typically it will be something like "java:comp/env/jms/TopicConnectionFactory"</li>
+	<li><b>cache.cluster.jms.topic.name</b> - The JNDI name of the topic that will be used for the OSCache sending the messages. This should match the name of a topic that is configured on your JMS server. Typically this value will be something like "java:comp/env/jms/OSCacheTopic".</li>
+	<li><b>cache.cluster.jms.node.name</b> - A name that uniquely identifies this node in the cluster. This is used to prevent nodes from processing their own broadcast messages. Each node in the cluster must have a different value, for example "node1", "node2", ... .</li>
+</ul>
+
+
+<p>If you are running OSCache from a standalone application or are not running in an environment where new InitialContext() will find your JNDI InitialContextFactory or provider URL, you will have to specify them either in a jndi.properties file or as system properties. See the InitalContext documentation for details.</p>
+
+<h3><a name="Clustering-JavaGroupsConfiguration"></a>JavaGroups Configuration</h3>
+
+<p>Just make sure you have jgroups-all.jar file in your classpath (for a webapp put it in WEB-INF/lib), and add the JavaGroups broadcasting listener to your oscache.properties file like this:</p>
+
+<div class="preformatted"><div class="preformattedContent">
+<pre>cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JavaGroupsBroadcastingListener
+</pre>
+</div></div>
+
+<p>In most cases, that's it! OSCache will now broadcast any cache flush events across the LAN. The jgroups-all.jar library is not included with the binary distribution due to its size, however you can obtain it either by downloading the full OSCache distribution, or by visiting the <a href="http://www.jgroups.org" title="Visit page outside Confluence">JavaGroups website</a>.</p>
+
+<p>If you want to run more than one OSCache cluster on the same LAN, you will need to use different multicast IP addresses. This allows the caches to exist in separate multicast groups and therefore not interfere with each other. The IP to use can be specified in your oscache.properties file by the cache.cluster.multicast.ip property. The default value is 231.12.21.132, however you can use any class D IP address. Class D address fall in the range 224.0.0.0 through 239.255.255.255.</p>
+
+<p>If you need more control over the multicast configuration (eg setting network timeout or time-to-live values), you can use the cache.cluster.properties configuration property. Use this instead of the cache.cluster.multicast.ip property. The default value is:</p>
+
+<div class="preformatted"><div class="preformattedContent">
+<pre>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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+UNICAST(timeout=300,600,1200,2400):\
+pbcast.STABLE(desired_avg_gossip=20000):\
+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)
+</pre>
+</div></div>
+
+<p>See the <a href="http://www.javagroups.com/" title="Visit page outside Confluence">JavaGroups</a> site for more information. In particular, look at the documentation of Channels in the <a href="http://www.javagroups.com/javagroupsnew/docs/newuser/index.html" title="Visit page outside Confluence">User's Guide</a>.</p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Complete Change Log.html b/docs/wiki/Complete Change Log.html
new file mode 100644
index 0000000..2bc88e1
--- /dev/null
+++ b/docs/wiki/Complete Change Log.html	
@@ -0,0 +1,2959 @@
+<html>
+<head>
+<title>OSCache - Complete Change Log</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<h2><a name="CompleteChangeLog-OSCache2.4.1"></a>OSCache 2.4.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(7th July 2007 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-Thismaintenancereleaseof2.4.1hastwobugfixes%3A"></a>This
+		maintenance release of 2.4.1 has two bug fixes:</h4>
+
+		<ul>
+			<li>The cacheFlushed method is not being invoked on the
+			CacheEntryEventListener</li>
+			<li>CacheFilter max-age parameter MAX_AGE_NO_INIT not set
+			properly</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(3 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-297"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-297">CACHE-297</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-297">max-age
+				parameter not set on ResponseContent object returned from cache when
+				using MAX_AGE_NO_INIT</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-296"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-296">CACHE-296</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-296">For
+				a Cache class the cacheFlushed method is not being invoked on the
+				CacheEntryEventListener.</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-279"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-279">CACHE-279</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-279">LRUCache
+				loses entries when updated by mutliple threads.</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.4"></a>OSCache 2.4</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(1st Mai 2007 - by Lars Torunski)</p>
+
+		<h4><a name="CompleteChangeLog-Newfeaturesandenhancements"></a>New
+		features and enhancements</h4>
+
+		<p>Furthermore the next major release 2.4 enhances the CacheFilter
+		and allows a better integration with the <a
+			href="http://www.springframework.org/"
+			title="Visit page outside Confluence">Spring Framework</a> and <a
+			href="JMX Monitoring.html" title="JMX Monitoring">JMX Monitoring</a>.</p>
+
+		<ul>
+			<li>Setting CacheFilter parameters runtime</li>
+			<li>Lazy initialization in CacheFilter in order to ease spring
+			integration</li>
+			<li>Allow disabling cacheing for special http methods (e.g.
+			POST/DELETE/PUT) in CacheFilter</li>
+			<li>CacheFilter allow reentrance over different filter
+			configurations</li>
+		</ul>
+
+
+		<ul>
+			<li>Hibernate 3.2 integration support</li>
+			<li>JMX Monitoring/Administration via Spring</li>
+			<li>Improve oscache.properties loading</li>
+			<li>Performance improvment for large disk persistence usage</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-UpgradeGuide"></a>Upgrade Guide</h4>
+
+		<ul>
+			<li>Due to the enhancements in the CacheFilter and method
+			signature changes, it's recommended to recompile your code.</li>
+			<li>Due to changes for CACHE-284 the handling of the listeners
+			have been changed: Before OSCache 2.4 objects which implemented
+			different CacheEventListener (e.g. CacheEntryEventListener and
+			CacheMapAccessEventListener) had to be added twice, because the
+			listeners where registrated only for one special event listener. With
+			OSCache 2.4 only the object has to be added to the list without the
+			2nd parameter 'type of the listener'.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC&tempMax=30&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(29 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-260"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-260">CACHE-260</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-260">NullPointerException
+				in AbstractConcurrentReadCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-295"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-295">CACHE-295</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-295">Hibernate
+				3.2 integration support</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-215"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-215">CACHE-215</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-215">Setting
+				CacheFilter parameters runtime</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-99"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-99">CACHE-99</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-99">Use
+				lazy initialization in cache filter in order to ease spring
+				integration</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-258"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-258">CACHE-258</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-258">NullPointerException
+				when using putCache(key, val) in LRUCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-273"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-273">CACHE-273</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-273">Update
+				to Commons Logging 1.1</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-253"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-253">CACHE-253</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-253">Migrate
+				from CVS to SVN</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-261"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-261">CACHE-261</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-261">Check
+				javadoc of Cache.cancelUpdate on key not being updated </a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-163"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/issue_subtask.gif"
+					alt="Sub-task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-163">CACHE-163</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-163">CacheFilter
+				easier sub-classing via pre- and post-processes</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-162"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/issue_subtask.gif"
+					alt="Sub-task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-162">CACHE-162</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-162">CacheFilter
+				easier sub-classing via useCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-272"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-272">CACHE-272</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-272">Allow
+				disabling of cacheing special http methods (e.g. POST/DELETE/PUT) in
+				CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-277"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-277">CACHE-277</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-277">CacheFilter
+				should allow reentrance over different filter configurations</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-283"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-283">CACHE-283</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-283">Improve
+				oscache.properties loading</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-266"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-266">CACHE-266</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-266">ServletCacheAdministrator
+				no longer a "Servlet Singleton"</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-267"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-267">CACHE-267</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-267">SplitServletOutputStream
+				doesn't pass flush() on to underlying stream</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-141"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-141">CACHE-141</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-141">CacheFilter
+				easier sub-classing</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-288"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-288">CACHE-288</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-288">Error
+				in the HashDiskPersistenceListener byteArrayToHexString</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-264"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-264">CACHE-264</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-264">problem
+				with not escaped group names and their filenames for disk
+				persistence</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-255"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-255">CACHE-255</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-255">
+				AbstractConcurrentReadCache#put(Object key, Object value) may return
+				a wrong value</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-249"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-249">CACHE-249</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-249">Performance
+				improvment for large disk persistence usage</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-293"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-293">CACHE-293</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-293">Allow
+				to specify a different oscache.properties file for Hibernate</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-278"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-278">CACHE-278</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-278">Filter
+				ignores max-age parameter when serving from cache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-284"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-284">CACHE-284</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-284">Cache.dispatchCacheEntryEvent
+				and Cache.addEventListener implementations are inconsistent</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-274"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-274">CACHE-274</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-274">new
+				method getIntialContext JMSBroadcastingListener</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-263"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-263">CACHE-263</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-263">Run
+				FindBugs 1.1.3 against current source code</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-292"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-292">CACHE-292</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-292">CacheFilter
+				max-age default and error-case initialisation are wrong</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-290"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-290">CACHE-290</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-290">Bad
+				Practice</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-178"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-178">CACHE-178</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-178">JMX
+				Monitoring/Administration via Spring</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-252"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-252">CACHE-252</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-252">Log
+				warning if user tries to set max entries on an unlimited cache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.3.2"></a>OSCache 2.3.2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(23rd July 2006 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-Thismaintenancereleaseof2.3.1hasoneenhancement%3A"></a>This
+		maintenance release of 2.3.1 has one enhancement:</h4>
+
+		<ul>
+			<li>The removeEntry method in the Cache removes the entry from
+			its groups now</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-Bugfixes%3A"></a>Bug fixes:</h4>
+
+		<ul>
+			<li>Method addGroupMappings leads to inconsistent memory cache
+			if a persistent cache group exists</li>
+			<li>Cache group is updated if entry is removed (duplicate)</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(3 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-244"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-244">CACHE-244</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-244">Cache
+				group is not updated if entry is removed</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-188"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-188">CACHE-188</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-188">removeEntry
+				should update group mappings</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-181"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-181">CACHE-181</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-181">addGroupMappings
+				leads to inconsistent Memory-Cache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.3.1"></a>OSCache 2.3.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(19th June 2006 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-Thismaintenancereleaseof2.3hasoneenhancement%3A"></a>This
+		maintenance release of 2.3 has one enhancement:</h4>
+
+		<ul>
+			<li>CacheFilter: Default initialization of the Cache-Control
+			max-age</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-Bugfixes%3A"></a>Bug fixes:</h4>
+
+		<ul>
+			<li>Cache.flushAll(Date flushDate) won't throw
+			NeedsRefreshException when flush date is not yet reached anymore</li>
+			<li>No NoSuchElementException at Cache.putInCache() anymore</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(3 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-246"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-246">CACHE-246</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-246">java.util.NoSuchElementException
+				during at com.opensymphony.oscache.base.Cache.putInCache()</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-241"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-241">CACHE-241</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-241">Cache.flushAll(Date
+				flushDate) throws NeedsRefreshException when flush date is not yet
+				reached</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-240"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-240">CACHE-240</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-240">Default
+				initialization of the Cache-Control max-age</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.3"></a>OSCache 2.3</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(6th March 2006 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-ThisreleaseincludesadditionalimprovementstotheCacheFilter%3A"></a>This
+		release includes additional improvements to the CacheFilter:</h4>
+
+		<ul>
+			<li>CRON expressions to expire content at specific dates and/or
+			times</li>
+			<li>Pluggable EntryRefreshPolicy</li>
+			<li>Reduced memory consumption</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-Diskpersistence%3A"></a>Disk
+		persistence:</h4>
+
+		<ul>
+			<li>Faster disk persistence</li>
+			<li>Avoid DiskPersistenceListener deadlocks if process has no
+			rights to delete cache file</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-Furtherchangesare%3A"></a>Further
+		changes are:</h4>
+
+		<ul>
+			<li>new JSP tag <em>addgroups</em></li>
+			<li>interface to get a list of the cache event listeners</li>
+			<li>commons collection dependency removed</li>
+			<li>Java 1.3 support dropped</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(14 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-235"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-235">CACHE-235</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-235">Pluggable
+				EntryRefreshPolicy for CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-230"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-230">CACHE-230</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-230">CacheFilter
+				Tutorial</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-229"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-229">CACHE-229</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-229">Tomcat
+				5.5.12 throws IllegalStateException on getId() / fixed Servlet Spec
+				2.4</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-228"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-228">CACHE-228</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-228">Add
+				CRON expressions to CacheFilter to expire content at specific dates
+				and/or times.</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-227"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-227">CACHE-227</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-227">Remove
+				commons collections from distribution</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-226"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-226">CACHE-226</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-226">Drop
+				Java 1.3 support</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-222"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-222">CACHE-222</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-222">Add
+				new JSP tag addGroups</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-217"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-217">CACHE-217</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-217">Avoid
+				DiskPersistenceListener deadlocks if process has no rights to delete
+				cache file</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-216"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-216">CACHE-216</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-216">Review
+				CacheFilter against Servlet 2.4 spec.</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-214"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-214">CACHE-214</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-214">Reduce
+				memory consumption of ResponseContent</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-200"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-200">CACHE-200</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-200">add
+				'getCacheEventListenerList()' to Cache class</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-197"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-197">CACHE-197</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-197">Speed
+				up disk persistence</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-183"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-183">CACHE-183</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-183">HashDiskPersistenceListener
+				/ MessageDigest not thread safe</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-136"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-136">CACHE-136</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-136">SequencedHashMap
+				is deprecated in commons collections 3.1</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.2Final"></a>OSCache 2.2
+		Final</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotesFinal"></a>Release
+		Notes - Final</h3>
+
+		<p>(6th November 2005 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-Additionallytothe2.2RCOSCache2.2RCimprovements%2Cthefinalreleasewasenhancedby%3A"></a>Additionally
+		to the <a href="OSCache 2.2 RC.html" title="OSCache 2.2 RC">2.2 RC</a>
+		improvements, the final release was enhanced by:</h4>
+
+		<ul>
+			<li>Allow cache group definition in CacheFilter</li>
+			<li>Option to specify when to send Expires-Header</li>
+			<li>Allow disabling initial set of the last modified header</li>
+			<li>Continuous Integration and Dependency Management with Ivy</li>
+			<li>Update to JGroups 2.2.8</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(12 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-223"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-223">CACHE-223</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-223">completeUpdate
+				never being called after startUpdate() has been called, OSCache
+				hangs for that key</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-211"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-211">CACHE-211</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-211">Create
+				check sums for the distribution files</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-210"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-210">CACHE-210</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-210">Review:
+				If last test-base and last test-web overwrite previous unit test
+				reports</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-204"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-204">CACHE-204</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-204">Allow
+				disabling initial set of the last modified header</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-203"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-203">CACHE-203</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-203">Change
+				JSP tag URI in pages of example war</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-202"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-202">CACHE-202</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-202">Expires
+				header should not be inital set in fragments</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-201"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-201">CACHE-201</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-201">Defined
+				interface for ICacheKeyProvider not used in CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-199"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-199">CACHE-199</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-199">Continuous
+				Integration and Dependency Management with Ivy</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-198"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-198">CACHE-198</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-198">Update
+				to JGroups 2.2.8</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-196"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-196">CACHE-196</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-196">Option
+				to specify when to send Expires-Header</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-195"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-195">CACHE-195</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-195">Allow
+				cache group generation in CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-194"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-194">CACHE-194</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-194">Update
+				Documentation</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.2RC"></a>OSCache 2.2 RC</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotesReleaseCandidate"></a>Release
+		Notes - Release Candidate</h3>
+
+		<p>(18th September 2005 - by Lars Torunski)</p>
+
+		<h4><a
+			name="CompleteChangeLog-Besidesbugsbeingfixed%2CmajorimprovementshavebeenmadetotheCacheFilterinmanyways%3A"></a>Besides
+		bugs being fixed, major improvements have been made to the CacheFilter
+		in many ways:</h4>
+
+		<ul>
+			<li>Default initialization of the last modified header which
+			reduces transaction overhead and server load</li>
+			<li>Support of GZip filters in the filter chain</li>
+			<li>Custom key generation by subclassing CacheFilter or by
+			implementing a special interface</li>
+			<li>Preserving more http headers, e.g. the expires header</li>
+			<li>Special handling for fragments of a page</li>
+			<li>Avoids session creation for application scope pages</li>
+			<li>Multiple matching cache filters won't dead-lock the response
+			anymore</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(22 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-189"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-189">CACHE-189</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-189">AbstractDiskPersistenceListener.store
+				hangs on exception</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-185"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-185">CACHE-185</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-185">Filtered
+				requests will be re-requested twice</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-184"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-184">CACHE-184</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-184">Filter
+				deadlock with external apps (mostly spiders)</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-179"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-179">CACHE-179</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-179">Provider
+				interface for method createCacheKey</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-174"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-174">CACHE-174</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-174">Regression
+				in fix of CACHE-170: UpdateStateEntry may leak when entry are
+				removed</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-173"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-173">CACHE-173</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-173">NullPointerException
+				while flushing inexistant group</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-170"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-170">CACHE-170</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-170">Data
+				race handling Cache.updateStates results in Thread hangs when the
+				blocking mode is used in concurrence</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-169"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-169">CACHE-169</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-169">Default
+				initialization of the last modified header</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-161"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/issue_subtask.gif"
+					alt="Sub-task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-161">CACHE-161</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-161">CacheFilter
+				easier sub-classing via isCacheable</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-160"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-160">CACHE-160</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-160">ExpiresRefreshPolicy
+				always set in CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-155"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-155">CACHE-155</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-155">Support
+				of GZip filters in the filter chain</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-154"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-154">CACHE-154</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-154">NullPointerException
+				in JavaGroupsBroadcastingListener</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-148"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-148">CACHE-148</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-148">getInstance
+				call not thread-safe</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-144"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-144">CACHE-144</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-144">CacheTag
+				doesn't clear variables in doStartTag / doFinally</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-143"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-143">CACHE-143</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-143">Report
+				expected expiry to clients/browsers/proxy</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-138"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-138">CACHE-138</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-138">Document
+				new parameters in the wiki</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-135"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-135">CACHE-135</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-135">CacheFilter
+				for fragements of a page</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-129"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-129">CACHE-129</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-129">CacheFilter
+				will create useless sessions for application-scope pages</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-128"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-128">CACHE-128</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-128">Multiple
+				matching filters will dead-lock the response</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-120"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-120">CACHE-120</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-120">New
+				nocache option when body contains a jsessionid</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-83"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-83">CACHE-83</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-83">CacheHttpServletResponseWrapper
+				& ResponseContent dont preserver Http headers</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-69"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-69">CACHE-69</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-69">Custom
+				Key Generation on CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.1.1"></a>OSCache 2.1.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(1st May 2005 - by Andres March)</p>
+
+		<h4><a name="CompleteChangeLog-Improvements%3A"></a>Improvements:</h4>
+
+		<ul>
+			<li>The taglib URI was changed to <tt><a
+				href="http://www.opensymphony.com/oscache"
+				title="Visit page outside Confluence">http://www.opensymphony.com/oscache</a></tt>
+			in <a href="http://jira.opensymphony.com/browse/CACHE-61"
+				title="Visit page outside Confluence">CACHE-61</a></li>
+			<li>The DiskPersistenceListener escapes '?' now and guarantees
+			that the filenames will be unique based on the cache key, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-110"
+				title="Visit page outside Confluence">CACHE-110</a></li>
+			<li>Session objects in cache tags are created only if necessary,
+			see <a href="http://jira.opensymphony.com/browse/CACHE-88"
+				title="Visit page outside Confluence">CACHE-88</a></li>
+			<li>The disk persistence configuration key can be accessed now,
+			see <a href="http://jira.opensymphony.com/browse/CACHE-111"
+				title="Visit page outside Confluence">CACHE-111</a></li>
+		</ul>
+
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li>The CacheFilter doesn't send back a 304 (not modified)
+			response when client cache is de-activated anymore, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-116"
+				title="Visit page outside Confluence">CACHE-116</a></li>
+			<li>CacheFilter doesn't support correctly i18N by setting
+			encoding not properly, <a
+				href="http://jira.opensymphony.com/browse/CACHE-38"
+				title="Visit page outside Confluence">CACHE-38</a> and <a
+				href="http://jira.opensymphony.com/browse/CACHE-159"
+				title="Visit page outside Confluence">CACHE-159</a></li>
+			<li>Cron expressions - leap days not always matched correctly, <a
+				href="http://jira.opensymphony.com/browse/CACHE-157"
+				title="Visit page outside Confluence">CACHE-157</a></li>
+			<li>FindBugs doesn't report that the usage of GetResource may be
+			unsafe if class Config is extended anymore, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-108"
+				title="Visit page outside Confluence">CACHE-108</a></li>
+			<li>ConcurrentModificationException on flushGroup, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-127"
+				title="Visit page outside Confluence">CACHE-127</a></li>
+			<li>Exception not thrown when not serializable object is
+			persisted instead stack trace is persisted, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-112"
+				title="Visit page outside Confluence">CACHE-112</a></li>
+			<li>A few concurrency issues were fixed, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-170"
+				title="Visit page outside Confluence">CACHE-170</a>, <a
+				href="http://jira.opensymphony.com/browse/CACHE-167"
+				title="Visit page outside Confluence">CACHE-167</a>, <a
+				href="http://jira.opensymphony.com/browse/CACHE-127"
+				title="Visit page outside Confluence">CACHE-127</a></li>
+		</ul>
+
+
+		<h4><a
+			name="CompleteChangeLog-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes
+		that may affect backwards compatibility:</h4>
+
+		<ul>
+			<li>The improvement <a
+				href="http://jira.opensymphony.com/browse/CACHE-88"
+				title="Visit page outside Confluence">CACHE-88</a> may change the
+			behaviour of the application, because a session object isn't created
+			anymore even if it wasn't necessary. A web application may react
+			different to a not existing session object.</li>
+			<li>The URI change of <a
+				href="http://jira.opensymphony.com/browse/CACHE-61"
+				title="Visit page outside Confluence">CACHE-61</a> from <tt>/oscache</tt>
+			to <tt><a href="http://www.opensymphony.com/oscache"
+				title="Visit page outside Confluence">http://www.opensymphony.com/oscache</a></tt>
+			affects all JSP's which explicit use the old URI.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(15 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-170"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-170">CACHE-170</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-170">Data
+				race handling Cache.updateStates results in Thread hangs when the
+				blocking mode is used in concurrence</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-167"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-167">CACHE-167</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-167">removeEntry
+				not synchronized</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-159"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-159">CACHE-159</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-159">CacheFilter
+				does not set encoding properly</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-157"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-157">CACHE-157</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-157">Cron
+				expressions - leap days not always matched correctly</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-131"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-131">CACHE-131</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-131">JavaDoc:
+				Missing class description - CacheContextListener</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-127"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-127">CACHE-127</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-127">ConcurrentModificationException
+				on flushGroup</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-116"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-116">CACHE-116</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-116">CacheFilter
+				sends back a 304 (not modified) response when client cache is
+				de-activated</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-112"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-112">CACHE-112</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-112">Exception
+				not thrown when not serializable object is persisted instead stack
+				trace is persisted!</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-111"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-111">CACHE-111</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-111">public
+				access for disk persistence configuration key</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-110"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-110">CACHE-110</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-110">DiskPersistenceListener
+				should escape '?'</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-109"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-109">CACHE-109</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-109">cache.blocking
+				parameter missing in oscache.properties</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-108"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-108">CACHE-108</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-108">FindBugs
+				reports: Usage of GetResource may be unsafe if class Config is
+				extended</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-88"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-88">CACHE-88</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-88">Don't
+				create session object in cache tags unless necessary</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-61"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-61">CACHE-61</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-61">Taglib
+				URI Attribute</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-38"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-38">CACHE-38</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-38">oscache
+				filter doesn't support correctly i18N</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.1"></a>OSCache 2.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(18th January 2005 - by Andres March)</p>
+		<h4><a name="CompleteChangeLog-NewFeatures%3A"></a>New Features:</h4>
+		<ul>
+			<li>Added HashDiskPersistenceListner <a
+				href="http://jira.opensymphony.com/browse/CACHE-132"
+				title="Visit page outside Confluence">CACHE-132 </a> that hashes
+			file names in order to eliminate nasty characters and overly long
+			names</li>
+			<li>Added property that allows cache entries to only be
+			persisted when the memory capacity has been exceeded. The property is
+			called: cache.persistence.overflow.only. It defaults to false for
+			backwards compatibility meaning all cache entries are persisted when
+			a listener has been registered. See <a
+				href="http://jira.opensymphony.com/browse/CACHE-133"
+				title="Visit page outside Confluence">CACHE-133 </a></li>
+			<li>Check If-Modified-Since header in cache filter to increase
+			performance, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-58"
+				title="Visit page outside Confluence">CACHE-58 </a> and <a
+				href="http://jira.opensymphony.com/browse/CACHE-70"
+				title="Visit page outside Confluence">CACHE-70 </a></li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-Improvements%3A"></a>Improvements:</h4>
+
+		<ul>
+			<li>Updated jgroups jar regarding changed package name <a
+				href="http://jira.opensymphony.com/browse/CACHE-85"
+				title="Visit page outside Confluence">CACHE-85 </a>, <a
+				href="http://jira.opensymphony.com/browse/CACHE-126"
+				title="Visit page outside Confluence">CACHE-126</a> and
+			configuration based upon recommendations from Bela Ban (javagroups
+			maintainer).</li>
+			<li>More evenly distributed disk caching, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-94"
+				title="Visit page outside Confluence">CACHE-94 </a></li>
+			<li>Public access for configuration properties, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-92"
+				title="Visit page outside Confluence">CACHE-92 </a></li>
+			<li>Public method to clear cache, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-104"
+				title="Visit page outside Confluence">CACHE-104 </a>, <a
+				href="http://jira.opensymphony.com/browse/CACHE-68"
+				title="Visit page outside Confluence">CACHE-68</a></li>
+			<li>Output the scope name's in toString() of
+			ScopeEventListenerImpl, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-95"
+				title="Visit page outside Confluence">CACHE-95 </a></li>
+			<li>Call get() method on put() method call, see <a
+				href="http://jira.opensymphony.com/browse/CACHE-105"
+				title="Visit page outside Confluence">CACHE-105 </a></li>
+			<li>Library updates
+			<ul>
+				<li>upgrade to Commons Logging 1.0.4, see <a
+					href="http://jira.opensymphony.com/browse/CACHE-102"
+					title="Visit page outside Confluence">CACHE-102 </a></li>
+				<li>upgrade to Commons Collections 3.1, see <a
+					href="http://jira.opensymphony.com/browse/CACHE-103"
+					title="Visit page outside Confluence">CACHE-103 </a></li>
+			</ul>
+			</li>
+			<li>Moved all docs to <a
+				href="http://wiki.opensymphony.com/display/CACHE/Home"
+				title="Visit page outside Confluence">wiki</a></li>
+			<li>Website documentation updates.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-73"
+				title="Visit page outside Confluence">CACHE-73 </a> -
+			NullpointerException after deserialization of
+			AbstractConcurrentReadCache</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-98"
+				title="Visit page outside Confluence">CACHE-98 </a> - Disk cache not
+			getting served first time for long keys</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-107"
+				title="Visit page outside Confluence">CACHE-107 </a> - flushEntry
+			does not behave correctly in cluster</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-118"
+				title="Visit page outside Confluence">CACHE-118</a> - Updating
+			groups doesn't work</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-119"
+				title="Visit page outside Confluence">CACHE-119</a> - flush does not
+			work correctly in a clustered environment</li>
+		</ul>
+
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="3" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(21 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-133"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-133">CACHE-133</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-133">added
+				cache.persistence.overflow.only property</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-132"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-132">CACHE-132</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-132">Added
+				HashDiskPersistenceListner</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-126"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-126">CACHE-126</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-126">java.lang.NoClassDefFoundError:
+				org/javagroups/blocks/NotificationBus$Consumer</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-119"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-119">CACHE-119</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-119">flush
+				does not work correctly in a clustered environment</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-118"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-118">CACHE-118</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-118">Updating
+				groups doesn't work</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-107"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-107">CACHE-107</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-107">flushEntry
+				does not behave correctly in cluster</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-105"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-105">CACHE-105</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-105">call
+				get() method on put() method call</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-104"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-104">CACHE-104</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-104">Destroy
+				cache</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-103"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-103">CACHE-103</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-103">upgrade
+				to Commons Collections 3.1</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-102"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-102">CACHE-102</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-102">upgrade
+				to Commons Logging 1.0.4 </a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-98"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-98">CACHE-98</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-98">Disk
+				cache not getting served first time for long keys</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-95"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-95">CACHE-95</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-95">Output
+				the scope name's in toString()</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-94"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-94">CACHE-94</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-94">More
+				evenly distributed disk caching</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-92"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-92">CACHE-92</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-92">public
+				access for configuration properties</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-89"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-89">CACHE-89</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-89">java.lang.NullPointerException
+				: AbstractCacheAdministrator.finalizeListeners</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-85"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-85">CACHE-85</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-85">upgrade
+				to JavaGroups 2.2.7</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-73"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-73">CACHE-73</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-73">NullpointerException
+				after deserialization of AbstractConcurrentReadCache</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-72"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-72">CACHE-72</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-72">NullPointerException
+				in AbstractConcurrentReadCache.clear</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-71"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-71">CACHE-71</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-71">Flush
+				and refresh of cached pages fail under heavy load</a></td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-70"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-70">CACHE-70</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-70">last
+				modified problem</a></td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-58"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-58">CACHE-58</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-58">
+				Check If-Modified-Since header in cache filter</a></td>
+			</tr>
+		</table></p>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.0.2"></a>OSCache 2.0.2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(22nd January 2004 - by Mathias Bogaert)</p>
+
+		<h4><a name="CompleteChangeLog-Improvements%3A"></a>Improvements:</h4>
+
+		<ul>
+			<li>Website documentation updates.</li>
+			<li>Added OSCache in the Wild.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-63"
+				title="Visit page outside Confluence">CACHE-63 </a>
+			NullPointerException in GeneralCacheAdministrator#destroy().</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-44"
+				title="Visit page outside Confluence">CACHE-44 </a> Multi threading
+			issues with LRU Cache.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-66"
+				title="Visit page outside Confluence">CACHE-66 </a>
+			DiskPersistenceListener is not Serializable.</li>
+			<li>GeneralCacheAdministrator now creates the cache from within
+			the constructor. This prevents possible threading issues if the cache
+			is not initialized during application startup.</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.0.1"></a>OSCache 2.0.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(4th November 2003 - by Chris Miller)</p>
+		<h4><a name="CompleteChangeLog-Improvements%3A"></a>Improvements:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-56"
+				title="Visit page outside Confluence">CACHE-56 </a> Refresh period
+			is no longer mandatory.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-51"
+				title="Visit page outside Confluence">CACHE-51 </a> Added an
+			<cache:addgroup /> tag. This allows cache groups to be
+			dynamically added from within a <cache:cache /> tag.</li>
+			<li>Website documentation is now bundled with the OSCache
+			distribution.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-59"
+				title="Visit page outside Confluence">CACHE-59 </a> Silent mode
+			could not be reset.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-60"
+				title="Visit page outside Confluence">CACHE-60 </a> Fixed deadlock
+			problem when cancelUpdate() was called while under load.</li>
+		</ul>
+
+
+		<h4><a
+			name="CompleteChangeLog-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes
+		that may affect backwards compatibility:</h4>
+
+		<ul>
+			<li>StringUtil.split() now returns a List rather than a
+			String[].</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.0"></a>OSCache 2.0</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(22nd September 2003 - by Chris Miller)</p>
+		<h4><a name="CompleteChangeLog-Improvements%3A"></a>Improvements:</h4>
+
+		<ul>
+			<li>Minor FastCronParser speedup.</li>
+			<li>Made ClusterNotification constants public.</li>
+			<li>Dropped some of the logging levels from INFO down to DEBUG.</li>
+			<li>Release has been split into two - a binary release and a
+			full release (includes source).</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-52"
+				title="Visit page outside Confluence">CACHE-52 </a> Fixed a problem
+			that caused no output on Tomcat for small JSP files.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-53"
+				title="Visit page outside Confluence">CACHE-53 </a> Updated
+			documentation to explain that a PersistenceListener must be specified
+			to enable caching to disk.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-55"
+				title="Visit page outside Confluence">CACHE-55 </a> JMS was throwing
+			an exception on Weblogic.</li>
+			<li>Altering the cache capacity on the fly using the
+			administrator classes wasn't working correctly.</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.0beta2"></a>OSCache 2.0
+		beta 2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(4th August 2003 - by Chris Miller)</p>
+		<h4><a name="CompleteChangeLog-NewFeatures%3A"></a>New Features:</h4>
+
+		<ul>
+			<li>Now supports JavaGroups version 2.1.</li>
+			<li>JMS Clustering support has been added -Romulus Pasca.</li>
+			<li>Clustering code has been refactored. As a result of this,
+			some of the clustering configuration has changed since beta 1 -
+			please see the updated clustering documentation for details.</li>
+			<li>Performance enhancement: When running under JRE 1.3.x, the
+			LRUCache will now attempt to use the Jakarta commons collections
+			SequencedHashMap. If the commons-collections.jar is not present then
+			the code resorts to using a LinkedList and a warning is logged. Note
+			that under JRE 1.4.x and higher the commons-collections.jar is not
+			required.</li>
+			<li>Config.getProperties() method added.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-48"
+				title="Visit page outside Confluence">CACHE-48 </a> FastCronParser
+			no longer requires JDK 1.4.x.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-45"
+				title="Visit page outside Confluence">CACHE-45 </a> Fixed a
+			serialization bug.</li>
+			<li>The CachewideEvent was not holding the event date.</li>
+			<li>Prevented an error from being logged in the CachewideEvent
+			handling (even though no problem had occurred).</li>
+			<li>Fixed a subtle bug in the concurrent unit test.</li>
+			<li>The ServletCacheAdministrator's app scope cache is created
+			on startup (via the CacheContextListener).</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache2.0beta1"></a>OSCache 2.0
+		beta 1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(19th July 2003 - by Chris Miller)</p>
+		<h4><a name="CompleteChangeLog-NewFeatures%3A"></a>New Features:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-11"
+				title="Visit page outside Confluence">CACHE-11 </a> Cache grouping
+			support. This allows cache entries to be placed into an arbitrary
+			group or groups and flushed with a single flushGroup() call.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-30"
+				title="Visit page outside Confluence">CACHE-30 </a> Added support
+			for expiring cache entries based on a cron expression. Entries that
+			are older than the date/time that most recently matches the cron
+			expression will be considered stale. This is exposed to the cache tag
+			via the 'cron' attribute. See cronTest.jsp for examples.</li>
+			<li>Event listener support has been refactored and improved. It
+			is now possible to specify a comma-delimited list of event listeners
+			using this property. Previously only one class could be specified.
+			Events listed here should implement the CacheEntryEventListener
+			and/or the ScopeEventListener interfaces.</li>
+			<li>New event CacheMapAccessEvent.STALE_HIT. This event is fired
+			when an attempt is made to retrieve and entry from the cache, and the
+			entry is found but is stale.</li>
+			<li>Clustering support has been added as an event listener.
+			Currently it is implemented using <a href="http://www.javagroups.com"
+				title="Visit page outside Confluence">JavaGroups </a>. To enable,
+			just add the BroadcastingCacheEventListener class to the
+			cache.event.listeners property.</li>
+			<li>Now uses Jakarta Commons Logging for all log messages. This
+			means that the cache.debug configuration property is now ignored -
+			use whatever logging configuration is appropriate for your logging
+			setup instead. -Fabian Crabus</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-14"
+				title="Visit page outside Confluence">CACHE-14, Matthias Nott </a>
+			Now allows for content to be cached indefinitely without expiration.</li>
+			<li>The build.xml <javac ...> directives now specify
+			debug="true".</li>
+			<li>Performance boost: When OSCache is running on JRE 1.4 or
+			higher, LRUCache and FIFOCache use a LinkedHashSet instead of a
+			LinkedList.</li>
+			<li>Japloy is now used to ensure source is consistently
+			formatted.</li>
+			<li>Test cases now work on non-windows platforms. Also coverage
+			reports added courtesy of clover.</li>
+		</ul>
+
+
+		<h4><a
+			name="CompleteChangeLog-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes
+		that may affect backwards compatibility:</h4>
+
+		<ul>
+			<li>The cache.entryevent.classes property in the configuration
+			file has been renamed to cache.event.listeners, since it accepts
+			CacheEntryEventListener, ScopeEventListener and
+			CacheMapAccessEventListener types.</li>
+			<li>The cache.persistence.classes property has been renamed to
+			cache.persistence.class since it is only possible to specify one
+			PersistenceListener.</li>
+			<li>For consistency, cache.unlimited_disk is now
+			cache.unlimited.disk and cache.useHostDomainInKey is now
+			cache.use.host.domain.in.key.</li>
+			<li>The oscache.tld file now uses a taglib 1.2 DTD.</li>
+			<li>To build OSCache, JDK 1.4.x or higher is required. There is
+			however no runtime dependency on JDK 1.4.x.</li>
+			<li>The Cache.flushPattern() method and <cache:flush
+			pattern="..."/> are deprecated. You are instead encouraged to
+			group your cache entries when you add them to the cache and then use
+			the Cache.flushGroup() method or the <cache:flush group="..."/>
+			tag to flush an entire cache group.</li>
+			<li>Disk persistence now puts all files in the same directory.
+			This has a number of side effects. Keys >255 chars will cause
+			problems. Also, similar keys might get mapped to the same file. For
+			example, it is very inadvisable to have two keys with the names
+			'my_key' and 'my.key'.</li>
+			<li>GeneralCacheAdministrator is no longer static. Users that
+			relied on this behaviour can still hold onto a static reference to it
+			with minor code changes.</li>
+			<li>When a NeedsRefreshException is thrown, it is now vital that
+			the cache entry is either updated, or Cache.cancelUpdate(key) is
+			called to release the lock on this cache entry. This is a consequence
+			of the fix for <a href="http://jira.opensymphony.com/browse/CACHE-42"
+				title="Visit page outside Confluence">CACHE-42 </a>.</li>
+			<li>CacheProperties class was removed. It didn't work on 1.7.5
+			anyway. The same effect can be achieved by specifying a subclass of
+			Properties.</li>
+			<li>Autogenerated cache keys now contain the request method (eg,
+			HEAD, GET, etc).</li>
+			<li>OSCache has been repackaged from
+			"com.opensymphony.module.oscache.*" to
+			"com.opensymphony.oscache.*". Any code or configuration files
+			that refer to "com.opensymphony.module.oscache" will need to be
+			updated.</li>
+		</ul>
+
+
+		<h4><a name="CompleteChangeLog-BugFixes%3A"></a>Bug Fixes:</h4>
+
+		<ul>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-4"
+				title="Visit page outside Confluence">CACHE-4 </a> WebSphere 3.5.x
+			compatibility.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-5"
+				title="Visit page outside Confluence">CACHE-5 </a> Added a mode
+			attribute to the cache tag to allow content to be cached but not sent
+			to the output stream. See oscacheTest.jsp for an example.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-7"
+				title="Visit page outside Confluence">CACHE-7 </a> "cache" Tag has
+			no "setEncoding" method.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-9"
+				title="Visit page outside Confluence">CACHE-9 </a> It could be
+			useful being able to specify directories relative to the web
+			application dir. for config file and cache dir. Use new properties
+			aware getInstance method.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-10"
+				title="Visit page outside Confluence">CACHE-10 </a> Cannot write and
+			use custom class implementing CacheProperties.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-13"
+				title="Visit page outside Confluence">CACHE-13 </a>
+			AbstractConcurrentReadCache loops indefinitely when persistRetrieve()
+			returns null.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-14"
+				title="Visit page outside Confluence">CACHE-14 </a> You can now
+			specify an unlimited refresh time by supplying a negative value for
+			the duration.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-17"
+				title="Visit page outside Confluence">CACHE-17 </a> An example war
+			is now included - "ant example-war". Once deployed this can be tested
+			using "ant test-web".</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-26"
+				title="Visit page outside Confluence">CACHE-26 </a> Security hole
+			whereby certain keys can overwrite any file.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-28"
+				title="Visit page outside Confluence">CACHE-28 </a> URLs can now be
+			used as keys with disk persistence.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-31"
+				title="Visit page outside Confluence">CACHE-31 </a> and <a
+				href="http://jira.opensymphony.com/browse/CACHE-33"
+				title="Visit page outside Confluence">CACHE-33 </a> The cache tag's
+			refresh attribute will now be taken into account even if a custom
+			refresh policy has been specified.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-34"
+				title="Visit page outside Confluence">CACHE-34 </a> Setting
+			properties with AbstractCacheAdministrators. New getInstance method
+			added to ServletCacheAdministrator that takes in properties.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-35"
+				title="Visit page outside Confluence">CACHE-35 </a> CacheFilter
+			needs to distinguish between HEAD and GET requests.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-39"
+				title="Visit page outside Confluence">CACHE-39 </a> and <a
+				href="http://jira.opensymphony.com/browse/CACHE-44"
+				title="Visit page outside Confluence">CACHE-44 </a> Synchronization
+			with LRUCache fixed.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-42"
+				title="Visit page outside Confluence">CACHE-42 </a> Threads will no
+			longer race to (re)build expired or new cache entries. By default
+			stale content will be served if available. This behaviour can be
+			changed by setting oscache.blocking=true, which will instead cause
+			threads to block until the new cache entry is available.</li>
+			<li><a href="http://jira.opensymphony.com/browse/CACHE-43"
+				title="Visit page outside Confluence">CACHE-43 </a> Taglibs have
+			been made spec-compliant. They now follow the guidelines at <a
+				href="http://jakarta.apache.org/taglibs/guidelines.html"
+				title="Visit page outside Confluence">http://jakarta.apache.org/taglibs/guidelines.html</a>.</li>
+			<li>Some synchronization issues were fixed in LRUCache.getItem()
+			and AbstractConcurrentReadCache.setMaxEntries().</li>
+			<li>ScopeEventListener classes were previously not able to be
+			specified in the configuration even though the dispatching code was
+			implemented. ScopeEventListeners can now be specified using the
+			cache.event.listeners configuration property.</li>
+			<li>CacheMapAccessEvents now only fire when an attempt is made
+			to retrieve the actual cache content for external use. Previously
+			these events were being fired in circumstances that were not of
+			statistical interest - for example HIT and MISS events were being
+			fired when updating or flushing entries from the cache.</li>
+			<li>Minor bug in oscacheTestMultipleTagNoKey.jsp - some of the
+			tag refresh times weren't correctly specified.</li>
+			<li>cachetest.jsp - the 'refresh' functionality wasn't working
+			because the addition of the refresh parameter caused the cache key to
+			be different. The key is now specified explicitly.</li>
+			<li>EntryRefreshPolicy is now serializable so it can be
+			persisted to the disk cache.</li>
+			<li>ServletCacheAdministrator now sorts request parameters and
+			filters out jsessionid so they have no impact on the generated cache
+			key.</li>
+			<li>CacheFilter only caches successful responses (status code ==
+			SC_OK).</li>
+		</ul>
+
+
+		<h4><a
+			name="CompleteChangeLog-KnownProblems%3A%28thesehaveexistedforsometimeinthe1.x.xversionsandwillbeaddressedinanupcoming2.x.xrelease%29"></a>Known
+		Problems: (these have existed for some time in the 1.x.x versions and
+		will be addressed in an upcoming 2.x.x release)</h4>
+
+		<ul>
+			<li>Session caches (created using the ServletCacheAdministrator)
+			have some known limitations:<br />
+			o Due to a workaround in the code, it is possible for a system under
+			heavy load to get its persistent session caches confused across
+			sessions.<br />
+			o Session caches will not work in a clustered environment.<br />
+			o Session caches have the same settings global settings applied to
+			them as the application scope cache. This means that if you want a
+			persistent cache for the application scope cache, the session caches
+			will use it too.</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.5"></a>OSCache 1.7.5</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+		<p>(5th January 2002 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Fixed up logging system slightly. All errors should now be
+			logged with logError() and normal messages with log()</li>
+			<li>Fixed bug in build file which put oscache.properties inside
+			the oscache.jar (resulting in it being loaded badly in some
+			containers)</li>
+			<li>Changed cache.capacity in the default oscache.properties
+			file to 1000. This means up to 1000 items will be cached in the
+			default setup, and LRUCache will be used (100 seemed too small)</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.4"></a>OSCache 1.7.4</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(3rd December 2001 - by Francois Beauregard,
+		fbeauregard at pyxis-tech.com, and<br />
+		Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Made all servlet cache components serializable (fixes bug
+			reported on list with JRun)</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.3"></a>OSCache 1.7.3</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(11th November 2001 - by Francois Beauregard,
+		fbeauregard at pyxis-tech.com)</p>
+
+		<ul>
+			<li>TestCacheEntry had a test method with improper name (flush
+			-> testFlush)</li>
+			<li>Pluggable entry refresh policy now available in the cache
+			tag</li>
+		</ul>
+
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.2"></a>OSCache 1.7.2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(31st October 2001 - by Mike Cannon-Brookes,
+		mike at atlassian.com)</p>
+
+		<ul>
+			<li>Cleaned up <em>all</em> JavaDoc messages to ensure
+			consistency and readability (removed unnecessary CVS tags, added
+			<code> and <pre> where needed, added @return and @param
+			to all methods)</li>
+			<li>Renamed nbMaxEntries to cacheCapacity and cache.size
+			property to cache.capacity (to fit better with Collections API where
+			capacity is max size, size is current size)</li>
+			<li>Renamed algoClass to algorithmClass for clarity.</li>
+			<li>Fixed up build.xml so that test classes are compiled to a
+			different location and not included in oscache.jar (to make it
+			smaller footprint)</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.1"></a>OSCache 1.7.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(26th September 2001 - by Francois Beauregard,
+		fbeauregard at pyxis-tech.com, and<br />
+		Alain Bergevin, abergevin at pyxis-tech.com, of Pyxis Technologies Inc.)</p>
+
+		<ul>
+			<li>Cache Events</li>
+			<li>Persistence mechanism refactored</li>
+			<li>Cache Algorithms FIFO + LRU (Limit the size of the cache)</li>
+			<li>AbstractConcurrentReadCache from Doug Lea's
+			ConcurrentReaderHashMap.<br />
+			Should give oscache performance improvement</li>
+			<li>Disk Persistence does not need any locking strategies.
+			Everything is handled by AbstractConcurrentReadCache</li>
+			<li>Pluggable entry refresh policies</li>
+			<li>Unlimited cache size for disk</li>
+			<li>Specify Duration using Simple Date Format or ISO-8601 as
+			suggested by Fredrik Lindgren)<br />
+			The next one that would make sense I think is being able to specify a
+			specific time of day.</li>
+		</ul>
+
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.7.0"></a>OSCache 1.7.0</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(26th September 2001 - by Francois Beauregard,
+		fbeauregard at pyxis-tech.com, and<br />
+		Alain Bergevin, abergevin at pyxis-tech.com, of Pyxis Technologies Inc.)</p>
+
+		<p>This version include some refactoring, corrections and new
+		features.<br />
+		Here are the highlights:</p>
+
+		<ul>
+			<li>CacheAdministrator has been split in 3. We have now
+			AbstractCacheAdministrator, and ServletCacheAdministrator and
+			GeneralCacheAdministrator extends it</li>
+			<li>Packages have been adjusted. We now have oscache.base,
+			oscache.general and oscache.Servlet.<br />
+			Adjustement must be made to the oscache.tld</li>
+			<li>ServletCacheHashMap has been created in order to reflect
+			specific needs for Servlets. It extends CacheHashMap</li>
+			<li>Support for multiple cache tag in a single page, without
+			supplying a key. Nested cache tag are not yet supported (you need to
+			manage keys in that case).</li>
+			<li>OSCache can now cache any objects (not only JSP content)
+			using GeneralCacheAdministrator</li>
+			<li>GenerateKey now support suffixes (used to deal with multiple
+			cache tags)</li>
+			<li>A complete JUnit test suite has been created for osCache,
+			including a JSP and a Servlet</li>
+			<li>Added the required libraries for the test unit. The JUnit
+			JAR has been upgraded to version 3.7</li>
+			<li>Required libraries are now HHTPUnit, Tidy, JUnit 3.7 and
+			JUnitPerf</li>
+			<li>The cBuffer variable used for keyGeneration has been moved
+			locally to GenerateKey since it was a threading issue</li>
+			<li>The build file has been modified to include test running</li>
+			<li>The flushAll method is now abstract since
+			CacheAbstractAdministrator can't know all valid scopes</li>
+			<li>Removed the retry logic for disk cache read and write (not
+			used anymore)</li>
+			<li>Fixed an issue with the needsRefresh method which returned
+			an invalid value when invoked first by returning true and then
+			invoked having to return false. Both case returned true.</li>
+			<li>The doStartTag method in CacheTag has been modified to
+			prevent returning null when cache content is missing (cache file
+			deleted)</li>
+			<li>The doAfterBody method in CacheTag has been modified in
+			order to prevent hitting the cache twice in some situation</li>
+			<li>The useBody method in CacheTag has been renamed to
+			setUseBody in order to reflect its usage</li>
+			<li>LoadProperties interface added to CacheProperties</li>
+			<li>Added a NeedsRefreshException</li>
+			<li>Retrofited the changed made by Kesav Kumar in order to
+			retrieve the sessionId correctly</li>
+			<li>Added code toughness to avoid working with invalid
+			parameters in public methods</li>
+			<li>Magic numbers and strings are now declared as constants</li>
+			<li>Many methods are now declared as final or protected</li>
+			<li>Imports are now more accurate, no more *</li>
+			<li>Comments and some headers modified to reflect JavaDoc
+			standard</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.6.1"></a>OSCache 1.6.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(16th September, 2001 - by Todd Gochenour,
+		tgochenour at peregrine.com)</p>
+
+		<ul>
+			<li>Removed attribute "encoding" in all areas, since Object
+			serialization stores strings in UTF-8 format, encoding is no longer
+			necessary.</li>
+			<li>Added Synchronization to getCacheEntry() to insure multiple
+			threads do not access HashMap and get erroneous results.</li>
+			<li>Implemented property cache.useHostDomainInKey (true/false)
+			to prepend URL request server name to cache key when cache used by
+			multiple servers. The "cache.domainname" property found in
+			oscache.properties (not used in code) was removed.</li>
+			<li>Corrected file caching logic so that multiple processes can
+			share cache information (file locking). Missing still is the ability
+			to signal processes that a cache needs to be flushed when using
+			Memory Caching along with File Caching.</li>
+			<li>Unit testing revealed some minor configuration bugs which
+			were corrected.</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.6"></a>OSCache 1.6</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(5th September, 2001 - by Mike Cannon-Brookes,
+		mike at atlassian.com)</p>
+
+		<ul>
+			<li>Changed the CacheEntry so that it caches Object rather than
+			String (allowing image caching) (Serge Knystautas,
+			sergek at lokitech.com)</li>
+			<li>Cached objects are now serialized to disk so cannot be read
+			by humans anymore (this allows us to cache Object) (Serge Knystautas,
+			sergek at lokitech.com)</li>
+			<li>Added a Servlet 2.3 CacheFilter (and associated response
+			classes) that caches whole requests (Serge Knystautas,
+			sergek at lokitech.com)</li>
+			<li>Minor changes to CacheAdministrator (the way Cache and
+			CacheEntry's are retrieved) - merging Serge and Todd's changes</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.5"></a>OSCache 1.5</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(6th August, 2001 - by Todd Gochenour,
+		tgochenour at peregrine.com)</p>
+
+		<ul>
+			<li>Added boolean "cache.memory" attribute to oscache.properties
+			to eliminate memory consumption and rely strictly on disk storage.</li>
+			<li>Added three interfaces "CacheLog", "CacheProperties", and
+			"CacheContents" to allow plugable implementations for these
+			functions. The CacheContents interface allows the pages to be cached
+			using a database.</li>
+			<li>Added "Language" attribute to CacheTag and FlushTag to
+			distinguish a page that supports I18N generation. The ISO-639
+			language code is used when the scope of the page is "Application".
+			The code defines a subdirectory under the "application" directory of
+			file caching.</li>
+			<li>Modified the CacheAdministrator.generateKey() function to
+			append the request's QueryString to the URI when automatically
+			generating keys. The QueryString is encoded using the MD5 digest
+			base64 algorithms.</li>
+			<li>Added attribute "encoding" to a CacheTag so that the file IO
+			does proper conversion when reading and writing the cache files. (per
+			suggestion of Pedro Gomez)</li>
+			<li>Added retries when SecurityException is thrown. Java has no
+			built in exclusive file locking implementations. The file is written
+			to a lock file and then renamed as an atomic operation so that
+			multiple processes on the same box can reliable access cache data.</li>
+			<li>Added "pattern" attribute to FlushTag which invokes a
+			CacheHashMap.flushPattern() function to scan for and flush all keys
+			that contain the value of the pattern. (per suggestion of Todd
+			Rudrick)</li>
+			<li>Added support for a CacheTag time value of zero which turns
+			off caching for that tag. (per suggestion of Pedro Gomez)</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.3"></a>OSCache 1.3</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(9th June, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Fixed a single bug in the file caching - should now work</li>
+			<li>Added property to set the cache key (not sure if this is
+			useful)</li>
+			<li>Cleaned up a lot of the code, refactored slightly so that
+			the tags are more light weight and rely more on the Administrator and
+			CacheHashMap for functionality.</li>
+		</ul>
+
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.2.5"></a>OSCache 1.2.5</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(18th May, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Added ability to turn off file caching (just remove or
+			comment out cache.properties)</li>
+			<li>Removed a pesky (but ineffectual) bug where session caches
+			being removed from disk were throwing NullPointerExceptions</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.2.1"></a>OSCache 1.2.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(10th May, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Speed improvements in CacheEntry and CacheAdministrator (by
+			Kesav Kumar - kesavk at voquette.com)</li>
+			<li>Fixed DOCTYPE in taglib.tld (also Kesav!)</li>
+			<li>Removed backup / swap / temp files from zip (and changed
+			build file)</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.2"></a>OSCache 1.2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(28th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Fixed a large bug that resulted in CacheEntry's not
+			refreshing. Large enough in a Caching library to demand a new point
+			release <img class="emoticon" src="./icons/emoticons/wink.gif"
+				height="20" width="20" align="absmiddle" alt="" border="0" /></li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.1"></a>OSCache 1.1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(25th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Moved up to 1.1 because a lot of documentation improving and
+			some small bug fixing has been done</li>
+			<li>Javadocs should now be very readable for all classes and
+			methods</li>
+			<li>Fixed a NullPointer that was being thrown in
+			CacheEntry.needsRefresh()</li>
+			<li>Cleaned up the build file so it now produces releasable zip
+			files easily</li>
+			<li>Added servlet.jar so that the compiling now works OOB (Out
+			Of the Box)</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.0beta2"></a>OSCache 1.0
+		beta 2</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(20th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+		<ul>
+			<li>Fixed more bugs.</li>
+			<li>Moved things around so that the CacheAdministrator has more
+			functionality and is now a Singleton (per web app context). This
+			means no more depedency on ServletContextListener to start the
+			CacheAdministrator.</li>
+			<li>Therefore we are now Servlet 2.2 / JSP 1.1 compliant! w00!</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.0beta1"></a>OSCache 1.0
+		beta 1</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(20th February, 2001 - by Mike Cannon-Brookes,
+		mike at atlassian.com)</p>
+
+		<ul>
+			<li>Fixed a few bugs.</li>
+			<li>Greatest of which there is no longer a disk leakage from
+			session caches on disk.</li>
+			<li>Also session caching bugs fixed, usecached bugs fixed - lots
+			of work done here.</li>
+			<li>Implemented to flush individual keys.</li>
+		</ul>
+
+
+		<h2><a name="CompleteChangeLog-OSCache1.0beta0"></a>OSCache 1.0
+		beta 0</h2>
+		<h3><a name="CompleteChangeLog-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(26th November, 2000 - by Mike Cannon-Brookes,
+		mike at atlassian.com)</p>
+
+		<ul>
+			<li>Initial release of OSCache</li>
+			<li>Conceptualised a few things I've been working on over the
+			past month.</li>
+			<li>Added persistent on disk caching and error tolerance
+			(through <usecached /> tag)</li>
+		</ul>
+
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/Configuration.html b/docs/wiki/Configuration.html
new file mode 100644
index 0000000..b6e6d78
--- /dev/null
+++ b/docs/wiki/Configuration.html
@@ -0,0 +1,134 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Configuration
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>This guide only covers the configuration of OSCache by using the oscache.properties file. To see how to install OSCache and where to place the oscache.properties file, see the <a href="Installation Guide.html" title="Installation Guide">Installation Guide</a>.<br/>
+The following properties are able to be set in the oscache.properties file:</p>
+
+<h4><a name="Configuration-cache.memory"></a>cache.memory</h4>
+
+<p>Valid values are true or false, with true being the default value. If you want to disable memory caching, just comment out or remove this line.</p>
+
+<p>Note: disabling memory AND disk caching is possible but fairly stupid <img class="emoticon" src="./icons/emoticons/wink.gif" height="20" width="20" align="absmiddle" alt="" border="0"/></p>
+
+<h4><a name="Configuration-cache.capacity"></a>cache.capacity</h4>
+
+<p>The maximum number of items that a cache will hold. By default the capacity is unlimited - the cache will never remove any items. Negative values will also be treated as meaning unlimited capacity.</p>
+
+<h4><a name="Configuration-cache.algorithm"></a>cache.algorithm</h4>
+
+<p>The 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 regardless of the value of this property. If you specify a size but not an algorithm, the cache algorithm used will be com.opensymphony.oscache.base.algorithm.LRUCache.</p>
+
+<p>OSCache currently comes with three algorithms:</p>
+
+<ul>
+	<li><b>com.opensymphony.oscache.base.algorithm.LRUCache</b> - Least Recently Used. This is the default when a cache.capacity is set.</li>
+	<li><b>com.opensymphony.oscache.base.algorithm.FIFOCache</b> - First In First Out.</li>
+	<li><b>com.opensymphony.oscache.base.algorithm.UnlimitedCache</b> - Content that is added to the cache will never be discarded. This is the default when no value is set for the cache.capacity property.</li>
+</ul>
+
+
+<h4><a name="Configuration-cache.blocking"></a>cache.blocking</h4>
+
+<p>When a request is made for a stale cache entry, it is possible that another thread is already in the process of rebuilding that entry. This setting specifies how OSCache handles the subsequent 'non-building' threads. The default behaviour (cache.blocking=false) is to serve the old content to subsequent threads until the cache entry has been updated. This provides the best performance (at the cost of serving slightly stale data). When blocking is enabled, threads will instead block unt [...]
+
+<p>Note that even if blocking is disabled, when there is no stale data available to be served threads will block until the data is added to the cache by the thread that is responsible for building the data.</p>
+
+<h4><a name="Configuration-cache.unlimited.disk"></a>cache.unlimited.disk</h4>
+
+<p>Indicates whether the disk cache should be treated as unlimited or not. The default value is false. In this case, the disk cache capacity will be equal to the memory cache capacity set by cache.capacity.</p>
+
+<h4><a name="Configuration-cache.persistence.class"></a>cache.persistence.class</h4>
+
+<p>Specifies the class to use for persisting cache entries. This class must implement the PersistenceListener interface. OSCache comes with an implementation that provides filesystem based persistence. Set this property to com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener to enable this implementation. By specifying your own class here you should be able to persist cache data using say JDBC or LDAP. NOTE: This class hashes the toString() of the object being cac [...]
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Note</b><br />
+<p>The HashDiskPersistenceListener and DiskPersistenceListener classes require <b>cache.path</b> to be set in order to know where to persist the files to disk.</p></td></tr></table>
+
+<h4><a name="Configuration-cache.path"></a>cache.path</h4>
+
+<p>This specifies the directory on disk where the caches will be stored. The directory will be created if it doesn't already exist, but remember that OSCache must have permission to write to this location. Avoid sharing the same cache path between different caches, because OSCache has not been designed to handle this.</p>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Note</b><br />
+<p>    For Windows machines, the backslash character '\' needs to be escaped. ie in Windows:</p></td></tr></table>
+<div class="preformatted"><div class="preformattedContent">
+<pre>    cache.path=c:\\myapp\\cache
+    or *ix:
+    cache.path=/opt/myapp/cache
+</pre>
+</div></div>
+
+<h4><a name="Configuration-cache.persistence.overflow.only%28NEW%21Since2.1%29"></a>cache.persistence.overflow.only  (NEW! Since 2.1)</h4>
+
+<p>Indicates whether the persistence should only happen once the memory cache capacity has been reached. The default value is false for backwards compatibility but the recommended value is true when the memory cache is enabled. This property drastically changes the behavior of the cache in that the persisted cache will now be different then what is in memory.</p>
+
+<h4><a name="Configuration-cache.event.listeners"></a>cache.event.listeners</h4>
+
+<p>This takes a comma-delimited list of fully-qualified class names. Each class in the list must implement one (or more) of the following interfaces:</p>
+
+<ul>
+	<li><b>CacheEntryEventListener</b> - Receives cache add/update/flush and remove events.</li>
+	<li><b>CacheMapAccessEventListener</b> - Receives cache access events. This allows you to keep statistical information to track how effectively the cache is working.</li>
+</ul>
+
+
+<p>No listeners are configured by default, however some ship with OSCache that you may wish to enable:</p>
+
+<ul>
+	<li><b>com.opensymphony.oscache.plugins.clustersupport.BroadcastingCacheEventListener</b> - provides clustering support for OSCache. Enabling this will cause cache flush events to be broadcast to other instances of OSCache running on your LAN. See <a href="Clustering.html" title="Clustering">Clustering OSCache</a> for further information about this event listener.</li>
+	<li><b>com.opensymphony.oscache.extra.CacheEntryEventListenerImpl</b> - a simple listener implementation that maintains a running count of all of the entry events that occur during a cache's lifetime.</li>
+	<li><b>com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl</b> - a simple listener implementation that keeps count of all the cache map events (cache hits and misses, and stale hits) that occur on a cache instance.</li>
+</ul>
+
+
+<p>It is also of course quite straightforward to write your own event listener. See the <a href="http://www.opensymphony.com/oscache/api" title="Visit page outside Confluence">JavaDoc API</a> for further details and <a href="Statistics.html" title="Statistics">Statistics</a> for an example.</p>
+
+
+<h4><a name="Configuration-cache.key"></a><a name="Configuration-cache.key"></a>cache.key</h4>
+
+<p>This is the key that will be used by the ServletCacheAdministrator (and hence the custom tags) to store the cache object in the application and session scope. The default value when this property is not specified is "__oscache_cache". If you want to access this default value in your code, it is available as com.opensymphony.oscache.web.ServletCacheAdministrator.DEFAULT_CACHE_KEY.</p>
+
+<h4><a name="Configuration-cache.use.host.domain.in.key"></a>cache.use.host.domain.in.key</h4>
+
+<p>If your server is configured with multiple hosts, you may wish to add host name information to automatically generated cache keys. If so, set this property to true. The default value is false.</p>
+
+
+<h3><a name="Configuration-AdditionalProperties"></a>Additional Properties</h3>
+
+<p>In additon to the above basic options, any other properties that are specified in this file will still be loaded and can be made available to your event handlers. For example, the JavaGroupsBroadcastingListener supports the following additional properties:</p>
+
+<h4><a name="Configuration-cache.cluster.multicast.ip"></a>cache.cluster.multicast.ip</h4>
+
+<p>The multicast IP to use for this cache cluster. Defaults to 231.12.21.132.</p>
+
+<h4><a name="Configuration-cache.cluster.properties"></a>cache.cluster.properties</h4>
+
+<p>Specifies additional configuration options for the clustering. The default setting is</p>
+<div class="preformatted"><div class="preformattedContent">
+<pre>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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+UNICAST(timeout=300,600,1200,2400):\
+pbcast.STABLE(desired_avg_gossip=20000):\
+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)
+</pre>
+</div></div>
+<p>See the <a href="Clustering.html" title="Clustering">Clustering</a> OSCache documentation for further details on the above two properties.</p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Cron Expressions.html b/docs/wiki/Cron Expressions.html
new file mode 100644
index 0000000..8fa057b
--- /dev/null
+++ b/docs/wiki/Cron Expressions.html	
@@ -0,0 +1,85 @@
+<html>
+    <head>
+        <title>OSCache - 
+         Expressions
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Prior to version 2.0 of <b>OSCache</b>, content expiry could only be specified in terms of how long a piece of content had been in the cache, ie, it was based on the age of the content. If you needed to expire it at a particular time of day or on a specific date, you had to write a custom RefreshPolicy class.</p>
+
+<p><b>OSCache 2.0</b> now gives you the ability to expire content at specific dates and/or times based on a cron expression.</p>
+
+<h3><a name="CronExpressions-WhatisaCronExpression%3F"></a>What is a Cron Expression?</h3>
+
+<p>Many of you are probably already familiar with the unix cron program. For those that aren't, cron is a daemon process that allows users to execute commands or scripts automatically at user-configurable dates and times. The important part as far as OSCache is concerned is the cron expression syntax that allows users to dictate when commands should be executed - you can now use the same syntax to expire content in OSCache! A cron expression is a simple text string that specifies particu [...]
+
+<h3><a name="CronExpressions-HowDoesOSCacheMatchAgainstanExpression%3F"></a>How Does OSCache Match Against an Expression?</h3>
+
+<p><b>OSCache</b> uses cron expressions in a manner that might seem 'backwards' to what you might initially expect. When using a cron expression to test if a cache entry is stale, OSCache finds the date and time (prior to the current time) that most recently matches the supplied expression. This date/time is used as the expiry time - entries that were placed in the cache prior to this expiry time are considered stale and result in a NeedsRefreshException being thrown.</p>
+
+<p>As an example, suppose you specify a cron expiry that matches every hour, on the hour ("0 * * * *"). If the current time is 10:42pm, then any content that was placed in the cache prior to 10:00pm would be considered stale.</p>
+
+<h3><a name="CronExpressions-WhatistheDifferenceBetweentheRefreshPeriodandaCronExpression%3F"></a>What is the Difference Between the Refresh Period and a Cron Expression?</h3>
+
+<p>The difference between the refresh period and a cron expression is that the refresh period specifies the maximum allowable age of a cache entry, whilst a cron expression specifies specific expiry times, regardless of how old an entry is. Eg imagine caching an object at 10:29am. With a refresh period of 30 minutes that entry would expire at 10:59am. With a cron expression of "0,30 * * * *" that entry would expire at 10:30am.</p>
+
+<h3><a name="CronExpressions-TheCronExpressionSyntax"></a>The Cron Expression Syntax</h3>
+
+<p>A cron expression consists of the following 5 fields:</p>
+
+<ul>
+	<li><b>Minute</b> - specifies what minute of the hour to expire content on. It is a number between 0 and 59.</li>
+	<li><b>Hour</b> - determines what hour of the day content will expire on. It is specified using the 24-hour clock, so the values must be between 0 (midnight) and 23 (11pm).</li>
+	<li><b>DOM</b> - the Day of the Month. This is a number from 1 to 31. It indicates what day the content should expire on. For example, to expire content on the 10th of every month, set this field to 10.</li>
+	<li><b>Month</b> - month of the year to expire the content. This can be specified either numerically (1 through 12), or by using the actual month name (eg 'January'). Month names are case-insensitive and only the first three characters are taken into account - the rest are ignored.</li>
+	<li><b>DOW</b> - The Day of the Week that the content should be expired on. This can be a numeric value (0-6, where 0 = Sunday, 1 = Monday, ..., 6 = Saturday), or you can use the actual day name. As is the case with month names, DOW names are case-insensitive and only the first three characters matter.</li>
+</ul>
+
+
+<p>If you don't want to specify a value for a particular field (ie you want the cron expression to match all values for that field), just use a * character for the field value.</p>
+
+<p>As an example, an expression that expired content at 11:45pm each day during April would look like this: "45 23 * April *".</p>
+
+<p><b>OSCache</b> also allows you to optionally specify lists, ranges and intervals (or even a combination of all three) within each field:</p>
+
+<ul>
+	<li><b>Lists</b> - items in a list are delimited using the ',' character. Content expiry times will be matched against all values in the list for that field. For example, "0,15,30,45 * * * *" will expire content every quarter-hour on the quarter hour.</li>
+	<li><b>Ranges</b> - ranges are specified using the '-' character. A range will include all values from the start to the end value (inclusive). For example, "* * * Jan-June *" will expire content every minute only during the first 6 months of the year.</li>
+	<li><b>Intervals</b> - an interval is specified using the '/' character. The value to the left of the '/' character indicates either the starting point or the range of values that should be incremented over, while the value to the right of the '/' specifies the interval or step size. Some examples -
+<div class="preformatted"><div class="preformattedContent">
+<pre>"10/20 * * * *" is equivalent to "10,30,50 * * * *", 
+while "10-45/20 * * * *" would only match 10 and 30 minutes past the hour, since 50 is outside the specified range.</pre>
+</div></div> 
+<div class="preformatted"><div class="preformattedContent">
+<pre>Supplying '*' as the left-hand value of an interval will match the same values as if you had specified a range over all possible values.
+Eg "*/10 * * * *" matches minutes 0,10,20,30,40 and 50.
+</pre>
+</div></div></li>
+</ul>
+
+
+<p>To have a look at further examples of both valid and invalid syntax, it is suggested you take a look at the JUnit test cases in the com.opensymphony.oscache.util.TestFastCronParser class. This class is located under the src/core/test directory. For examples of how to specify cron expiry times using the taglibs, see the <a href="JSP Tags.html" title="JSP Tags">Tag Reference</a> and the cronTest.jsp file in the example web application.</p>
+
+<h3><a name="CronExpressions-Notes"></a>Notes</h3>
+
+<ul>
+	<li>You can specify both a cron expression and a refresh interval at the same time if you like. This is useful in cases where you always want to expire content at midnight, but you also never want it to be more than 6 hours old.</li>
+	<li>Specifying out of range values, such as a 13 in the month field, will cause a ParseException to be thrown.</li>
+	<li>If a DOM is specified that cannot exist given the allowable months, a ParseException will be thrown. For example, "* * 31 Feb *" will fail because no date will ever match the 31st February!</li>
+	<li>The DOM and DOW fields cannot both be specified at the same time. One must always be set to '*' otherwise a ParseException will be thrown.</li>
+	<li>Leap years and local daylight savings time are taken into account. Eg "0 0 29 Feb *" will match midnight on the 29th February, ie only once every 4 years.</li>
+	<li>Currently the time used to match the cron expression against is always based on the local time on the server. If there is demand support for specifying an alternate timezone may</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Documentation.html b/docs/wiki/Documentation.html
new file mode 100644
index 0000000..6adadbe
--- /dev/null
+++ b/docs/wiki/Documentation.html
@@ -0,0 +1,81 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Documentation
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="Documentation-1.Overview"></a>1. Overview</h3>
+<ol>
+	<li><a href="What is OSCache.html" title="What is OSCache">What is OSCache</a></li>
+	<li><a href="License.html" title="License">License</a></li>
+	<li><a href="Feature List.html" title="Feature List">Feature List</a> - Details on OSCache's features and how they are best used.</li>
+	<li><a href="Requirements.html" title="Requirements">Requirements</a> - What is required to run OSCache.</li>
+	<li><a href="FAQ.html" title="FAQ">FAQ</a> - Frequently Asked Questions about OSCache.</li>
+	<li><a href="OSCache in the Wild.html" title="OSCache in the Wild">OSCache in the Wild</a> - A list of sites that are using OSCache in production.</li>
+</ol>
+
+
+<h3><a name="Documentation-2.OSCacheversions"></a>2. OSCache versions</h3>
+<ol>
+	<li><a href="Change Log.html" title="Change Log">Change Log</a> - See what's new in the latest version of OSCache (see also <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:changelog-panel" title="Visit page outside Confluence">JIRA - Change Log</a>).</li>
+	<li><a href="Roadmap.html" title="Roadmap">Roadmap</a> - See the expected future releases of OSCache (see also <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:roadmap-panel" title="Visit page outside Confluence">JIRA - Road Map</a>).</li>
+	<li>Build and Test Reports - <a href="http://www.opensymphony.com/oscache/junit/index.html" title="Visit page outside Confluence">Unit Test Results</a>, <a href="http://www.opensymphony.com/oscache/clover/index.html" title="Visit page outside Confluence">Clover Coverage Report</a> and <a href="http://www.opensymphony.com/oscache/dependencies/opensymphony-oscache-default.html" title="Visit page outside Confluence">Ivy Dependency Report</a></li>
+</ol>
+
+
+<h3><a name="Documentation-3.Tutorial"></a>3. Tutorial</h3>
+<ol>
+	<li><a href="Installation Guide.html" title="Installation Guide">Installation Guide</a> - How to install OSCache and where to get it.</li>
+	<li><a href="CacheFilter Tutorial.html" title="CacheFilter Tutorial">CacheFilter Tutorial</a> - How to cache entire pages of your website.</li>
+	<li><a href="Clustering.html" title="Clustering">Clustering OSCache</a> - How to use OSCache in a cluster.</li>
+	<li><a href="Statistics.html" title="Statistics">Statistics</a> - How to implement a cache listener to provide cache hits and misses information.</li>
+	<li><a href="JMX Monitoring.html" title="JMX Monitoring">JMX Monitoring</a> - JMX Monitoring and Administration via Spring.</li>
+	<li><a href="API Usage.html" title="API Usage">API Usage</a> - How to use the GeneralCacheAdministrator.</li>
+	<li>Taking the load off: <a href="http://www.theserverside.com/articles/article.tss?l=OSCacheHelpsDatabases" title="Visit page outside Confluence">OSCache helps database cope</a> by Andres March.</li>
+	<li><a href="SVN and Compiling OSCache.html" title="SVN and Compiling OSCache">SVN and Compiling OSCache</a> - How to access CVS and compile OSCache.</li>
+</ol>
+
+
+<h3><a name="Documentation-4.ReferenceGuide"></a>4. Reference Guide</h3>
+<ol>
+	<li><a href="Configuration.html" title="Configuration">Configuration</a> - Configuration options for OSCache.</li>
+	<li><a href="JSP Tags.html" title="JSP Tags">JSP Tags</a> - Detailed documentation on OSCache's tags and how to use them.</li>
+	<li><a href="CacheFilter.html" title="CacheFilter">CacheFilter Configuration</a> - How to configure OSCache to cache entire servlet responses.</li>
+	<li><a href="Cron Expressions.html" title="Cron Expressions">Cron Expressions</a> - How to use the cron expression syntax to expire content.</li>
+	<li><a href="http://www.opensymphony.com/oscache/api/" title="Visit page outside Confluence">JavaDoc API</a> - The OSCache API documentation in JavaDoc format.</li>
+</ol>
+
+
+<h3><a name="Documentation-5.Thirdpartyintegration"></a>5. Third-party integration</h3>
+<ol>
+	<li><a href="Hibernate.html" title="Hibernate">Hibernate</a> - How to integrate OSCache with Hibernate.</li>
+	<li><a href="Hibernate 2.1 and pre OSCache 2.4 support.html" title="Hibernate 2.1 and pre OSCache 2.4 support">Hibernate 2.1 and pre OSCache 2.4 support</a> - Hibernate 2.1 integration and pre OSCache 2.4 classes.</li>
+	<li><a href="Spring.html" title="Spring">Spring</a> - Simple configuration with no special support from Spring (taken from the <a href="http://opensource.atlassian.com/confluence/spring/display/INTEGRATE/OSCache" title="Visit page outside Confluence">Spring Integration</a>).</li>
+	<li><a href="http://www.jpox.org/docs/1_1/cache.html" title="Visit page outside Confluence">JPOX</a> - Integration of OSCache with JPOX transparent persistence JDO implementation.</li>
+</ol>
+
+
+<h3><a name="Documentation-6.Links"></a>6. Links</h3>
+<ol>
+	<li><a href="http://www.opensymphony.com/oscache/" title="Visit page outside Confluence">Current Release Home</a></li>
+	<li><a href="http://forums.opensymphony.com/forum.jspa?forumID=4" title="Visit page outside Confluence">Support Forum</a></li>
+	<li><a href="https://oscache.dev.java.net/servlets/ProjectDocumentList" title="Visit page outside Confluence">Download Source and Binaries</a></li>
+	<li>The current development release is available by the <a href="http://ivyrep.opensymphony.com/opensymphony/oscache/" title="Visit page outside Confluence">Ivy Repository</a></li>
+	<li><a href="http://svn.opensymphony.com/svn/oscache/" title="Visit page outside Confluence">SVN Access</a> beginning with release 2.4 or <a href="https://oscache.dev.java.net/source/browse/oscache/" title="Visit page outside Confluence">CVS Access</a> until release 2.3.2</li>
+	<li><a href="https://oscache.dev.java.net/" title="Visit page outside Confluence">dev.java.net Project</a></li>
+	<li><a href="https://oscache.dev.java.net/servlets/ProjectMailingListList" title="Visit page outside Confluence">Mailing List</a></li>
+</ol>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/FAQ.html b/docs/wiki/FAQ.html
new file mode 100644
index 0000000..eab405e
--- /dev/null
+++ b/docs/wiki/FAQ.html
@@ -0,0 +1,174 @@
+<html>
+    <head>
+        <title>OSCache - 
+        FAQ
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Got a question you'd like to ask? Ask us and we'll add it to the <a href="" title="FAQ">FAQ</a>.</p>
+
+<p>Questions</p>
+
+<ul>
+	<li><a href="#FAQ-uses" title="uses on FAQ">What can I use OSCache for exactly? </a></li>
+	<li><a href="#FAQ-data" title="data on FAQ">Where is the data cached? </a></li>
+	<li><a href="#FAQ-objects" title="objects on FAQ">Can OSCache cache Java objects rather than portions of JSP pages? I mean if I create a Product object, can I cache it and use it later so that I don't have to fetch data again? </a></li>
+	<li><a href="#FAQ-features" title="features on FAQ">What other features does OSCache have? </a></li>
+	<li><a href="#FAQ-tags" title="tags on FAQ">Can you give me some examples of how the OSCache tags are used? </a></li>
+	<li><a href="#FAQ-nesting" title="nesting on FAQ">Can OSCache tags be nested? </a></li>
+	<li><a href="#FAQ-size" title="size on FAQ">What control do you have over the cache size? I can imagine the size of the in-memory cache getting very big. Is it possible to set a max cache size and then remove the least-recently-used entries from the cache? </a></li>
+	<li><a href="#FAQ-algorithm" title="algorithm on FAQ">How does OSCache decide which object to remove? What caching algorithm does OSCache use? </a></li>
+	<li><a href="#FAQ-clustering" title="clustering on FAQ">How does OSCache's clustering work? </a></li>
+	<li><a href="#FAQ-expire" title="expire on FAQ">What happens if I need to expire data in the cache? </a></li>
+	<li><a href="#FAQ-groups" title="groups on FAQ">Can you tell me more about grouping cache entries? How might this be used? </a></li>
+	<li><a href="#FAQ-API" title="API on FAQ">I don't want to use the taglibs, I want to access OSCache directly from within my application. Where do I start? </a></li>
+</ul>
+
+
+<ul>
+	<li><a href="#FAQ-help" title="help on FAQ">Where else can I go for help if I can't find an answer to my question here? </a></li>
+</ul>
+
+
+
+<h4><a name="FAQ-WhatcanIuseOSCacheforexactly%3F"></a><a name="FAQ-uses"></a>What can I use OSCache for exactly?</h4>
+
+<p>OSCache can be used on three different levels:</p>
+
+<ul>
+	<li>JSP Caching - The first and simplest approach is to use the supplied taglibs to cache portions of JSP pages after the page has been rendered. This sounds simple but it's remarkably powerful and useful for almost every JSP application.</li>
+	<li>Request Caching - OSCache comes with a filter that allows you to cache entire HTTP responses - including dynamically generated images!</li>
+	<li>General-Purpose Cache - You can also use OSCache as a general-purpose caching solution by calling its API directly from your code. This allows arbitrary Java objects to be cached, and provides more control over the cache's behaviour. Note that any Java application can use OSCache in this manner; there is no requirement for a web server to be present when calling the API directly.</li>
+</ul>
+
+
+<p>All three approaches can be mixed and matched within the same application.</p>
+
+<h4><a name="FAQ-Whereisthedatacached%3F"></a><a name="FAQ-data"></a>Where is the data cached?</h4>
+
+<p>Out of the box, OSCache is capable of caching data in memory (so it is very fast), and/or to disk (so your cache can be persistent across server restarts). Support is also provided for managing a cluster of caches across multiple servers.</p>
+
+<p>In addition to these capabilities, it is possible to plug in custom persistence code and custom event handlers, so you could easily extend OSCache to persist cached objects to say a database or an LDAP directory.</p>
+
+<h4><a name="FAQ-CanOSCachecacheJavaobjectsratherthanportionsofJSPpages%3FImeanifIcreateaProductobject%2CcanIcacheitanduseitlatersothatIdon%27thavetofetchdataagain%3F"></a><a name="FAQ-objects"></a>Can OSCache cache Java objects rather than portions of JSP pages? I mean if I create a Product object, can I cache it and use it later so that I don't have to fetch data again?</h4>
+
+<p>Yes, however to do this you will need to write code that talks to the OSCache API directly. The taglibs are currently only designed to cache rendered JSP content. This should hopefully not be too big a limitation since any creation or manipulation of java objects should generally be performed in beans or MVC action classes rather than JSP scriptlets anyway.</p>
+
+<h4><a name="FAQ-WhatotherfeaturesdoesOSCachehave%3F"></a><a name="FAQ-features"></a>What other features does OSCache have?</h4>
+
+<p>There is a full list of features in the <a href="Feature List.html" title="Feature List">Feature List</a> documentation.</p>
+
+<h4><a name="FAQ-CanyougivemesomeexamplesofhowtheOSCachetagsareused%3F"></a><a name="FAQ-tags"></a>Can you give me some examples of how the OSCache tags are used?</h4>
+<div class="code"><div class="codeHeader"><b>Example 1</b></div><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><cache:cache time=<span class="code-quote">"600"</span>></span>
+        <span class="code-tag"><%= myBean.getTitle() %></span>
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div>
+<p>This will only access your EJB once every 10 minutes. Every other request it will just serve the cached JSP content that was produced the first time (this results in much faster page loading).</p>
+
+<div class="code"><div class="codeHeader"><b>Example 2</b></div><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><cache:cache key=<span class="code-quote">"foobar"</span> scope=<span class="code-quote">"session"</span>></span>
+        <span class="code-tag"><%= myBean.getTitle() %></span>
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div>
+<p>This time the cache is keyed (you could have a programmatic key here too, like <%= foobarString %>) and it's scoped by session.</p>
+
+<p>This is revolutionary as far as caching goes. You can now have cached content, that's different for every user! No more full page caches with no dynamic content!</p>
+
+<div class="code"><div class="codeHeader"><b>Example 3</b></div><div class="codeContent">
+<pre class="code-xml">(a very powerful & useful way to use the taglibs):
+
+    <span class="code-tag"><cache:cache></span>
+        <span class="code-tag"><% try { %></span>
+            <span class="code-tag"><%= myBean.getTitle() %></span>>
+        <span class="code-tag"><% } catch (Exception e) { %></span>
+            <span class="code-tag"><% application.log(<span class="code-quote">"Exception occurred in myBean.getTitle(): "</span> + e); %></span>
+            <span class="code-tag"><cache:usecached /></span>
+        <span class="code-tag"><% } %></span>
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div>
+<p>If a RemoteException occurs trying to get the EJB title (for example the database goes down) the cached content will be served so the user will not suspect a thing. No error page as per a normal JSP application. What does this mean? It means greater error tolerance in your JSP apps!</p>
+
+<p>One example of where this is useful - when our machine restarts, our app server loads faster than the database server. No problem - because the cache is persistent, it serves cached content while the database boots, then seamlessly kicks in to the database for a cache refresh when the database is ready.</p>
+
+<p>See the <a href="JSP Tags.html" title="JSP Tags">Tag Reference</a> and the example web application for further taglib examples.</p>
+
+<h4><a name="FAQ-CanOSCachetagsbenested%3F"></a><a name="FAQ-nesting"></a>Can OSCache tags be nested?</h4>
+
+<p>You can't currently nest <cache> tags within one another - not that you'd probably want to. It is because of the cache object being placed in the page scope for use by programmers within the tag.</p>
+
+<p>We're not sure if anyone actually uses this so we might remove it to allow for tag nesting (presumably across includes or something).</p>
+
+<h4><a name="FAQ-Whatcontroldoyouhaveoverthecachesize%3FIcanimaginethesizeoftheinmemorycachegettingverybig.Isitpossibletosetamaxcachesizeandthenremovetheleastrecentlyusedentriesfromthecache%3F"></a><a name="FAQ-size"></a>What control do you have over the cache size? I can imagine the size of the in-memory cache getting very big. Is it possible to set a max cache size and then remove the least-recently-used entries from the cache?</h4>
+
+<p>You can limit the memory cache by the number of objects that are cached. When an object is added to the cache and the limit is exceeded, another object will be removed from the cache to make room.</p>
+
+<p>Currently the disk cache can either be set to unlimited, or tied to the same size as the memory cache (ie, objects will be removed from the disk cache at the same time as they are removed from the memory cache. Depending on the useage patterns of your cache, restarting your application could mean that the disk cache might continue to grow). We understand that this is not ideal and there is room for improvement here. Stay tuned!</p>
+
+<h4><a name="FAQ-HowdoesOSCachedecidewhichobjecttoremove%3FWhatcachingalgorithmdoesOSCacheuse%3F"></a><a name="FAQ-algorithm"></a>How does OSCache decide which object to remove? What caching algorithm does OSCache use?</h4>
+
+<p>The caching algorithm is configurable. OSCache currently ships with 3 different algorithms - LRU (Least Recently Used), FIFO (First In First Out), and Unlimited. Should one of those not prove suitable, it is also possible to specify a custom algorithm class.</p>
+
+<h4><a name="FAQ-HowdoesOSCache%27sclusteringwork%3F"></a><a name="FAQ-clustering"></a>How does OSCache's clustering work?</h4>
+
+<p>The clustering is implemented as a listener that catches 'flush' events. These events are then broadcast across the network (using either the JavaGroups library or JMS) so that other nodes in the cluster can flush the relevant object(s) from their local cache. Note that for performance reasons, when objects are added to a cache they are not broadcast to other nodes. This means that each node in the cluster maintains their own relatively indedependent cache, yet still remains fresh.</p>
+
+<p>If this mechanism does not suit your requirements, you can always code up a different solution by writing a custom event handler.</p>
+
+<h4><a name="FAQ-WhathappensifIneedtoexpiredatainthecache%3F"></a><a name="FAQ-expire"></a>What happens if I need to expire data in the cache?</h4>
+
+<p>Cache entries can be flushed explicitly in several ways:</p>
+
+<ul>
+	<li>Individual entries can be flushed by specifying the cache key of the entry to flush eg <cache:flush key="myKey" scope="application"/></li>
+	<li>When adding an entry to the cache, it can optionally be placed into one or more groups. An entire group of entries can then be flushed by specifying the name of the group to flush. eg <cache:flush group="group1" scope="application"/></li>
+	<li>A pattern can be specified; all keys that contain the supplied pattern will be flushed. eg <cache:flush pattern="menu" scope="application"/> will flush all keys that contain the string "menu". (note that this approach is now deprecated. The cache grouping is more flexible and performs better than pattern flushing.)</li>
+</ul>
+
+
+<p>In addition, cached data can be expired at retrieval time by specifying a maximum age for the data, or by indicating what dates and/or times the data should expire. See the time, duration and cron attributes of the <cache> tag for more information.</p>
+
+
+<h4><a name="FAQ-Canyoutellmemoreaboutgroupingcacheentries%3FHowmightthisbeused%3F"></a><a name="FAQ-groups"></a>Can you tell me more about grouping cache entries? How might this be used?</h4>
+
+<p>This is a powerful feature that makes it easy to manage your cache content. Suppose you are rendering a website and the pages that you are caching depend on various factors. Perhaps they use various shared templates, some database content, and maybe some of them depend on an external data feed. By creating a cache group for each of these factors, each cached page can be placed into the group(s) that the page is dependent on. Then when say an external datafeed is updated it is trivial  [...]
+
+
+<div class="code"><div class="codeHeader"><b>Example 1:displayProduct.jsp</b></div><div class="codeContent">
+<pre class="code-xml">...
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"myKey1"</span> groups=<span class="code-quote">"product100,datafeed"</span>></span>
+        <span class="code-tag"><%= myProductBean.getProduct(100).getName() %></span>
+        <span class="code-tag"><%= myDatafeedBean.getDataFeed().getTotal() %></span>
+    <span class="code-tag"></cache:cache></span>
+    ...</pre>
+</div></div>
+<div class="code"><div class="codeHeader"><b>Example 2:updateDatafeed.jsp</b></div><div class="codeContent">
+<pre class="code-xml">...
+    <span class="code-tag"><%= myDatafeedBean.refreshDatafeed() %></span>
+
+    <span class="code-tag"><%-- Flush all cache entries that depend on the datafeed --%></span>
+    <span class="code-tag"><cache:flush group=<span class="code-quote">"datafeed"</span> scope=<span class="code-quote">"application"</span>></span>
+    ...</pre>
+</div></div>
+
+<h4><a name="FAQ-Idon%27twanttousethetaglibs%2CIwanttoaccessOSCachedirectlyfromwithinmyapplication.WheredoIstart%3F"></a><a name="FAQ-API"></a>I don't want to use the taglibs, I want to access OSCache directly from within my application. Where do I start?</h4>
+
+<p>We'd suggest the best place to start would be to look at the GeneralCacheAdministrator class. It provides a simple wrapper for a single cache instance and should give you all the basic functionality you need. If you want to work with multiple caches or manipulate your cache beyond what GeneralCacheAdministrator provides, consider either writing your own administrator class using GeneralCacheAdministrator as a starting point, or just create and use the Cache class directly. See the Jav [...]
+
+<h4><a name="FAQ-WhereelsecanIgoforhelpifIcan%27tfindananswertomyquestionhere%3F"></a><a name="FAQ-help"></a>Where else can I go for help if I can't find an answer to my question here?</h4>
+
+<p>The best place to try is on the OSCache <a href="https://oscache.dev.java.net/servlets/ProjectMailingListList" title="Visit page outside Confluence">mailing list</a>. It reaches a wide audience and is your best chance of getting a fast response. Remember to search the archives first to see if your question has already been answered.</p>
+
+<p>Got a question you'd like to ask? <a href="http://wiki.opensymphony.com/display/CACHE/FAQ?showComments=true#addcomment" title="Visit page outside Confluence">Ask us</a> and we'll add it to the <a href="" title="FAQ">FAQ</a>. </p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Feature List.html b/docs/wiki/Feature List.html
new file mode 100644
index 0000000..84b7deb
--- /dev/null
+++ b/docs/wiki/Feature List.html	
@@ -0,0 +1,133 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Feature List
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="FeatureList-OSCacheFeatures"></a>OSCache Features</h3>
+
+<h4><a name="FeatureList-Fastinmemorycaching"></a>Fast in-memory caching</h4>
+
+<ul>
+	<li>OSCache allows you to store dynamic content (eg for 30 minutes) in memory. Each further request is served directly from the memory cache, resulting in dramatic speed increases.</li>
+	<li>The cache is keyed programmatically. This means you can calculate a cache key that works for your situation. For example an ecommerce site might use product ID as keys, or content site might use an article date and article ID combination.</li>
+	<li>The cache is stored in standard scopes that any JSP programmer is familiar with (application or session). The session scope allows you to have different cached content per user. This is one unlike any other caching system we've ever seen.</li>
+</ul>
+
+
+<h4><a name="FeatureList-Persistentondiskcaching"></a>Persistent on-disk caching</h4>
+
+<ul>
+	<li>OSCache can also write the cache to disk. This provides caching across server restarts, and caching of datasets that do not fit into memory. Caching can be configured to use memory or file caching, or a combination of both.</li>
+	<li>If you want to persist the cache to somewhere other than disk, you can plug in a custom PersistenceListener. This allows you to persist the cache to anywhere (for example to a database via JDBC or to LDAP via JNDI).</li>
+	<li>When using both disk caching and memory caching. It is possible to limit the cache size to avoid using too much memory but let disk cache unlimited, resulting in browser style complementary disk cache. When cached objects are removed from memory, they are still on disk. If the item is needed again and it is not expired the cache file will be used. This also gives fault tolerance if the server crashes.</li>
+	<li>Persistence can also be switched to overflow mode using the property oscache.persistence.overflow.only. This changes the default behavior (of persisting every cache entry when there is a listener) to only persist when the memory cache capacity has been reached.</li>
+</ul>
+
+
+<h4><a name="FeatureList-ExcellentPerformance"></a>Excellent Performance</h4>
+
+<ul>
+	<li>Written with performance in mind.</li>
+	<li>Mulitple cache requests can be handled concurrently.</li>
+	<li>Only one requesting thread needs to update an expired cache entry even if multiple threads are requesting it simultaneously. Other threads can be configured to either receive the recently-expired object, or block until the cached object is updated. Similarly, when a new entry is being added to the cache, other threads requesting that entry will block until it is ready rather than run off and race to build the same object. In a high load environment this can provide enormous performa [...]
+	<li>Automatically takes advantage of JRE 1.4 or higher if available.</li>
+</ul>
+
+
+<h4><a name="FeatureList-Clusteringsupport"></a>Clustering support</h4>
+
+<ul>
+	<li>OSCache can easily be configured to cluster across multiple boxes. This provides both scalability and failover support without any changes required in your caching code.</li>
+</ul>
+
+
+<h4><a name="FeatureList-FlexibleCachingSystem"></a>Flexible Caching System</h4>
+
+<ul>
+	<li>OSCache allows you to cache portions of JSP pages, arbitrary Java objects, and even entire servlet responses.</li>
+	<li>Cache capacity can be set allowing you to limit the number of cached objects.</li>
+	<li>Multiple caching algorithms are supported such as LRU (Least Recently Used), FIFO (First In First Out), or unlimited. It is also possible to plug in your own custom algorithm.</li>
+	<li>You are given a huge amount of control over the way cached objects expire. Objects can be cached indefinitely, expired once they reach a certain age, or expired based on a cron expression. Programmatic flushing is also possible, and if that is still not enough pluggable RefreshPolicies allow custom refresh strategies.</li>
+	<li>Cached objects can be grouped together however you like, allowing for powerful management of cached data. This is an extremely useful feature that is far more powerful than what other caching solutions typically offer (such as the flushing of cache keys that match a particular pattern).</li>
+	<li>Fully event driven! OSCache fires events for various happenings 'under the hood' such as cache entry events (adding, updating, flushing and removing) and cache accesses (hit, stale hit and miss). It is easy to add your own event handlers.</li>
+	<li>Multiple caches can be created, each with their own unique configuration.</li>
+</ul>
+
+
+<h4><a name="FeatureList-SimpleJSPTagLibrary"></a>Simple JSP Tag Library</h4>
+
+<ul>
+	<li>The <a href="JSP Tags.html" title="JSP Tags">tag library</a> to perform and control the caching is very simple. See the Tag Reference for more information.</li>
+</ul>
+
+
+<h4><a name="FeatureList-CachingFilter"></a>Caching Filter</h4>
+
+<ul>
+	<li>A Servlet 2.3 Filter allows for caching of entire pages and generated binary files (like dynamically created images or PDF files).</li>
+	<li>Caching of binary files is extremely useful when they are generated dynamically and the average creation time is long.</li>
+	<li>The <a href="CacheFilter.html" title="CacheFilter">CacheFilter</a> is using the last modified header to reduce excellently the transaction overhead and server load which speed ups the server response time.</li>
+</ul>
+
+
+<h4><a name="FeatureList-ComprehensiveAPI"></a>Comprehensive API</h4>
+
+<ul>
+	<li>For the ultimate control, OSCache can be used through its straightforward API. You can instantiate, configure and control multiple caches programmatically. It would be possible for example to create one small in-memory cache that held currency conversion rates and was updated daily at 2am, while another cache could be purely disk based and used for holding dynamically created images.</li>
+</ul>
+
+
+<h4><a name="FeatureList-ExceptionHandling"></a>Exception Handling</h4>
+
+<ul>
+	<li>OSCache provides a way for your site to gracefully tolerate errors. This is not error prevention, rather if an error occurs it should not stop your site from functioning. For example if your database goes down, normally your product descriptions will not be browsable. Using OSCache you can cache those descriptions so you can still browse them.</li>
+</ul>
+
+
+<h4><a name="FeatureList-CacheFlushing"></a>Cache Flushing</h4>
+
+<ul>
+	<li>Flushing of caches can be controlled via JSP Tags, so these functions can easily be built into your administration interface.</li>
+	<li>There is programmatic control over what caches are flushed (eg all caches or just a particular scope).</li>
+	<li>Cached objects can be expired in a number of ways. Objects can be told to expire once they reach a certain age, or, through the use of cron expressions, on particular dates and/or times (eg it is trivial to make an object expire every weekday at 3am). If this is not enough, you can expire objects programmatically as required, or plug in your own custom RefreshPolicy class that can dynamically decide when an object should be flushed.</li>
+	<li>Entire groups of objects can be easily flushed from the cache. For example suppose you were caching product data as well as entire pages of your website. When a product was updated, you could flush not just the product object but also all the pages that contain information about that product. No more waiting for the cached objects to expire before the updated content shows up on your site!</li>
+</ul>
+
+
+<h4><a name="FeatureList-Portablecaching"></a>Portable caching</h4>
+
+<ul>
+	<li>Pure Java, this means it is platform independent.</li>
+	<li>OSCache is compliant with Servlet 2.3 and JSP 1.2 standards, which means it should work in the latest generation of servlet containers and application servers.</li>
+</ul>
+
+
+<h4><a name="FeatureList-i18nAware"></a>i18n Aware</h4>
+
+<ul>
+	<li>The caching is i18n aware and supports all encodings.</li>
+</ul>
+
+
+<h4><a name="FeatureList-SolidReputation"></a>Solid Reputation</h4>
+
+<ul>
+	<li>Thousands of downloads, hundreds of users on the <a href="https://oscache.dev.java.net/servlets/ProjectMailingListList" title="Visit page outside Confluence">mailing list</a>.</li>
+	<li>Comprehensive <a href="http://www.junit.org/" title="Visit page outside Confluence">JUnit</a> test suite that covers every aspect of OSCache, including a web stress test and various concurrent cache access scenarios. To back this up, the kind folks at <a href="http://www.cortexebusiness.com.au/" title="Visit page outside Confluence">Cortex</a> have supplied us with a <a href="http://www.thecortex.net/clover/" title="Visit page outside Confluence">Clover</a> license to provide detail [...]
+	<li>We have solid <a href="http://jira.opensymphony.com/" title="Visit page outside Confluence">issue tracking</a> using <a href="http://www.atlassian.com/software/jira" title="Visit page outside Confluence">JIRA</a> to keep track of any feature requests, bug reports and development progress. <a href="http://www.atlassian.com/software/jira" title="Visit page outside Confluence">JIRA</a> is provided courtesy of <a href="http://www.atlassian.com/" title="Visit page outside Confluence">Atl [...]
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate 2.1 Cache Adapter.html b/docs/wiki/Hibernate 2.1 Cache Adapter.html
new file mode 100644
index 0000000..fe107d3
--- /dev/null
+++ b/docs/wiki/Hibernate 2.1 Cache Adapter.html	
@@ -0,0 +1,119 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Hibernate 2.1 Cache Adapter
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Patched version of OSCache.java originally created by Mathias Bogaert.</p>
+
+<div class="code" style="border-style: solid; "><div class="codeHeader" style="border-bottom-style: solid; "><b>OSCache.java</b></div><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> java.util.Properties;
+
+<span class="code-keyword">import</span> net.sf.hibernate.cache.Cache;
+<span class="code-keyword">import</span> net.sf.hibernate.cache.CacheException;
+<span class="code-keyword">import</span> net.sf.hibernate.cache.Timestamper;
+<span class="code-keyword">import</span> net.sf.hibernate.util.PropertiesHelper;
+<span class="code-keyword">import</span> net.sf.hibernate.util.StringHelper;
+
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Config;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.CacheEntry;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.NeedsRefreshException;
+<span class="code-keyword">import</span> com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+/**
+ * Adapter <span class="code-keyword">for</span> the OSCache implementation
+ */
+<span class="code-keyword">public</span> class OSCache <span class="code-keyword">implements</span> Cache {
+    
+    /** 
+     * The <tt>OSCache</tt> cache capacity property suffix. 
+     */
+    <span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_CAPACITY = <span class="code-quote">"cache.capacity"</span>;
+
+    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> Properties OSCACHE_PROPERTIES = <span class="code-keyword">new</span> Config().getProperties();
+	/** 
+	 * The OSCache 2.0 cache administrator. 
+	 */
+	<span class="code-keyword">private</span> <span class="code-keyword">static</span> GeneralCacheAdministrator cache = <span class="code-keyword">new</span> GeneralCacheAdministrator();
+
+    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-object">Integer</span> capacity = PropertiesHelper.getInteger(OSCACHE_CAPACITY, OSCACHE_PROPERTIES);
+
+    <span class="code-keyword">static</span> {
+        <span class="code-keyword">if</span> (capacity != <span class="code-keyword">null</span>) cache.setCacheCapacity(capacity.intValue());
+    }
+    
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">int</span> refreshPeriod;
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span> cron;
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span> regionName;
+    <span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span>[] regionGroups;
+	
+	<span class="code-keyword">private</span> <span class="code-object">String</span> toString(<span class="code-object">Object</span> key) {
+		<span class="code-keyword">return</span> <span class="code-object">String</span>.valueOf(key) + StringHelper.DOT + regionName;
+	}
+
+	<span class="code-keyword">public</span> OSCache(<span class="code-object">int</span> refreshPeriod, <span class="code-object">String</span> cron, <span class="code-object">String</span> region) {
+		<span class="code-keyword">this</span>.refreshPeriod = refreshPeriod;
+		<span class="code-keyword">this</span>.cron = cron;
+		<span class="code-keyword">this</span>.regionName = region;
+        <span class="code-keyword">this</span>.regionGroups = <span class="code-keyword">new</span> <span class="code-object">String</span>[] {region};
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">Object</span> get(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-keyword">try</span> {
+			<span class="code-keyword">return</span> cache.getFromCache( toString(key), refreshPeriod, cron );
+		}
+		<span class="code-keyword">catch</span> (NeedsRefreshException e) {
+			cache.cancelUpdate( toString(key) );
+			<span class="code-keyword">return</span> <span class="code-keyword">null</span>;
+		}
+	}
+
+	<span class="code-keyword">public</span> void put(<span class="code-object">Object</span> key, <span class="code-object">Object</span> value) <span class="code-keyword">throws</span> CacheException {
+		cache.putInCache( toString(key), value, regionGroups );
+	}
+
+	<span class="code-keyword">public</span> void remove(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		cache.flushEntry( toString(key) );
+	}
+
+	<span class="code-keyword">public</span> void clear() <span class="code-keyword">throws</span> CacheException {
+		cache.flushGroup(regionName);
+	}
+
+	<span class="code-keyword">public</span> void destroy() <span class="code-keyword">throws</span> CacheException {
+		<span class="code-keyword">synchronized</span> (cache) {
+		    cache.destroy();
+        }
+	}
+
+	<span class="code-keyword">public</span> void lock(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-comment">// local cache, so we use synchronization
+</span>	}
+
+	<span class="code-keyword">public</span> void unlock(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-comment">// local cache, so we use synchronization
+</span>	}
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> nextTimestamp() {
+		<span class="code-keyword">return</span> Timestamper.next();
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">int</span> getTimeout() {
+		<span class="code-keyword">return</span> CacheEntry.INDEFINITE_EXPIRY;
+	}
+
+}</pre>
+</div></div> 
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate 2.1 Cache Provider.html b/docs/wiki/Hibernate 2.1 Cache Provider.html
new file mode 100644
index 0000000..7ea432c
--- /dev/null
+++ b/docs/wiki/Hibernate 2.1 Cache Provider.html	
@@ -0,0 +1,96 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Hibernate 2.1 Cache Provider
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Patched version of OSCacheProvider.java originally created by Mathias Bogaert.</p>
+
+<div class="code" style="border-style: solid; "><div class="codeHeader" style="border-bottom-style: solid; "><b>OSCacheProvider.java</b></div><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> java.util.Properties;
+
+<span class="code-keyword">import</span> net.sf.hibernate.cache.Cache;
+<span class="code-keyword">import</span> net.sf.hibernate.cache.CacheException;
+<span class="code-keyword">import</span> net.sf.hibernate.cache.CacheProvider;
+<span class="code-keyword">import</span> net.sf.hibernate.cache.Timestamper;
+<span class="code-keyword">import</span> net.sf.hibernate.util.PropertiesHelper;
+<span class="code-keyword">import</span> net.sf.hibernate.util.StringHelper;
+
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.CacheEntry;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Config;
+
+/**
+ * Support <span class="code-keyword">for</span> OpenSymphony OSCache. This implementation assumes
+ * that identifiers have well-behaved <tt>toString()</tt> methods.
+ */
+<span class="code-keyword">public</span> class OSCacheProvider <span class="code-keyword">implements</span> CacheProvider {
+
+	/** 
+	 * The <tt>OSCache</tt> refresh period property suffix. 
+	 */
+	<span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_REFRESH_PERIOD = <span class="code-quote">"refresh.period"</span>;
+	/** 
+	 * The <tt>OSCache</tt> CRON expression property suffix. 
+	 */
+	<span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_CRON = <span class="code-quote">"cron"</span>;
+	
+	<span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> Properties OSCACHE_PROPERTIES = <span class="code-keyword">new</span> Config().getProperties();
+
+	/**
+	 * Builds a <span class="code-keyword">new</span> {@link Cache} instance, and gets it's properties from the OSCache {@link Config}
+	 * which reads the properties file (<code>oscache.properties</code>) from the classpath.
+	 * If the file cannot be found or loaded, an the defaults are used.
+	 *
+	 * @param region
+	 * @param properties
+	 * @<span class="code-keyword">return</span>
+	 * @<span class="code-keyword">throws</span> CacheException
+	 */
+	<span class="code-keyword">public</span> Cache buildCache(<span class="code-object">String</span> region, Properties properties) <span class="code-keyword">throws</span> CacheException {
+
+		<span class="code-object">int</span> refreshPeriod = PropertiesHelper.getInt(
+			StringHelper.qualify(region, OSCACHE_REFRESH_PERIOD), 
+			OSCACHE_PROPERTIES, 
+			CacheEntry.INDEFINITE_EXPIRY
+		);
+		<span class="code-object">String</span> cron = OSCACHE_PROPERTIES.getProperty( StringHelper.qualify(region, OSCACHE_CRON) );
+
+		<span class="code-comment">// construct the cache        
+</span>        <span class="code-keyword">return</span> <span class="code-keyword">new</span> OSCache(refreshPeriod, cron, region);
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> nextTimestamp() {
+		<span class="code-keyword">return</span> Timestamper.next();
+	}
+
+	/**
+	 * Callback to perform any necessary initialization of the underlying cache implementation
+	 * during SessionFactory construction.
+	 *
+	 * @param properties current configuration settings.
+	 */
+	<span class="code-keyword">public</span> void start(Properties properties) <span class="code-keyword">throws</span> CacheException {
+	}
+
+	/**
+	 * Callback to perform any necessary cleanup of the underlying cache implementation
+	 * during SessionFactory.close().
+	 */
+	<span class="code-keyword">public</span> void stop() {
+	}
+
+}</pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate 2.1 and pre OSCache 2.4 support.html b/docs/wiki/Hibernate 2.1 and pre OSCache 2.4 support.html
new file mode 100644
index 0000000..f309f56
--- /dev/null
+++ b/docs/wiki/Hibernate 2.1 and pre OSCache 2.4 support.html	
@@ -0,0 +1,68 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Hibernate 2.1 and pre OSCache 2.4 support
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Info</b><br />
+<p>This page is intended to give integration support for Hibernate 2.1 and for pre OSCache 2.4 releases.<br/>
+It's recommended to use the new <a href="Hibernate.html" title="Hibernate">Hibernate 3.2</a> classes.</p></td></tr></table>
+
+<p><a href="http://www.hibernate.org/" title="Visit page outside Confluence">Hibernate</a> is a powerful, ultra-high performance object/relational persistence and query service for Java. Hibernate lets you develop persistent objects following common Java idiom - including association, inheritance, polymorphism, composition and the Java collections framework. Extremely fine-grained, richly typed object models are possible.</p>
+
+<p>Hibernate 2.1 features support for plugin cache providers and is designed to integrate with distributed caches (2.1 also implements more aggressive use of the cache). <b>net.sf.hibernate.cache.CacheProvider</b> is the extension point for user-defined cache integration.</p>
+
+<p><a href="http://www.hibernate.org/6.html" title="Visit page outside Confluence">Hibernate 2.1.1</a> or higher is required.</p>
+
+<h3><a name="Hibernate2.1andpreOSCache2.4support-hibernate.cache.providerclass"></a>hibernate.cache.provider_class</h3>
+
+<p>OSCache and Hibernate 2.1 integrate though OSCacheProvider. </p>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='warningMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/forbidden.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Warning</b><br />
+<p>Unfortunately, at this time the OSCacheProvider distributed with Hibernate 2.1.x is not suited to clustering with OSCache.  A patch has been submitted to the Hibernate team but not yet applied.  Even if you do not require clustering, we recommend use of these patched versions of <a href="Hibernate 2.1 Cache Provider.html" title="Hibernate 2.1 Cache Provider">OSCacheProvider</a> and <a href="Hibernate 2.1 Cache Adapter.html" title="Hibernate 2.1 Cache Adapter">OSCache</a> for Hibernate [...]
+
+<p>To enable OSCache in Hibernate's configuration, add the following line to hibernate.cfg.xml:</p>
+
+<div class="code"><div class="codeHeader"><b>hibernate.cfg.xml</b></div><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><property name=<span class="code-quote">"hibernate.cache.provider_class"</span>></span>my.patched.provider.package.OSCacheProvider<span class="code-tag"></property></span></pre>
+</div></div>
+
+<p>The default refresh period is <a href="http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/base/CacheEntry.html#INDEFINITE_EXPIRY" title="Visit page outside Confluence">CacheEntry.INDEFINITE_EXPIRY</a>. The first time a cacheable query is done, the cache has no effect on speed. On the second and successive queries, the cache will be populated and available to be hit.</p>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Object Identifiers</b><br />
+<p>Object identifiers must have well-behaved toString() methods, because OSCache uses the toString() method for the key of the cache.  Therefore it needs to create a unique identifier for the object being stored.</p></td></tr></table>
+
+<h3><a name="Hibernate2.1andpreOSCache2.4support-CacheRegionConfiguration"></a>Cache Region Configuration</h3>
+
+<p>To modify the refresh period, CRON expression, add the region configuration to your <a href="Configuration.html" title="Configuration">oscache.properties</a> file, as demonstrated below:</p>
+
+<div class="preformatted"><div class="preformattedContent">
+<pre>[region].refresh.period = 4000
+[region].cron = * * 31 Feb *
+
+# The maximum cache capacity can only be set per region if you use the 
+# net.sf.hibernate.cache.OSCacheProvider distributed with Hibernate.
+[region].capacity = 5000
+
+# The patched version distributed with OSCache only allows a single cache.capacity setting and saves memory.
+</pre>
+</div></div>
+
+<p>The com.mypackage.domain.Customer is Hibernate's internal cache region, which defaults to the classname, and which can be altered by setting Hibernate's configuration property <a href="http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html" title="Visit page outside Confluence">hibernate.cache.region_prefix</a> .</p>
+
+<h3><a name="Hibernate2.1andpreOSCache2.4support-SourceCode"></a>Source Code</h3>
+
+<ul><li><a href="Hibernate 2.1 Cache Adapter.html" title="Hibernate 2.1 Cache Adapter">Hibernate 2.1 Cache Adapter</a></li><li><a href="Hibernate 2.1 Cache Provider.html" title="Hibernate 2.1 Cache Provider">Hibernate 2.1 Cache Provider</a></li><li><a href="Hibernate 3 Cache Adaptor.html" title="Hibernate 3 Cache Adaptor">Hibernate 3 Cache Adaptor</a></li><li><a href="Hibernate 3 Cache Provider.html" title="Hibernate 3 Cache Provider">Hibernate 3 Cache Provider</a></li></ul>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate 3 Cache Adaptor.html b/docs/wiki/Hibernate 3 Cache Adaptor.html
new file mode 100644
index 0000000..06fbcc3
--- /dev/null
+++ b/docs/wiki/Hibernate 3 Cache Adaptor.html	
@@ -0,0 +1,146 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Hibernate 3 Cache Adaptor
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Patched version of OSCache.java for Hibernate 3 - originally created by Mathias Bogaert.</p>
+
+<div class="code" style="border-style: solid; "><div class="codeHeader" style="border-bottom-style: solid; "><b>OSCache.java</b></div><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> java.util.Properties;
+<span class="code-keyword">import</span> java.util.Map;
+
+<span class="code-keyword">import</span> org.hibernate.util.PropertiesHelper;
+<span class="code-keyword">import</span> org.hibernate.util.StringHelper;
+<span class="code-keyword">import</span> org.hibernate.cache.*;
+
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Config;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.CacheEntry;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.NeedsRefreshException;
+<span class="code-keyword">import</span> com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+/**
+ * Adapter <span class="code-keyword">for</span> the OSCache implementation
+ */
+<span class="code-keyword">public</span> class OSCache <span class="code-keyword">implements</span> Cache {
+    
+    /** 
+     * The <tt>OSCache</tt> cache capacity property suffix. 
+     */
+    <span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_CAPACITY = <span class="code-quote">"cache.capacity"</span>;
+
+    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> Properties OSCACHE_PROPERTIES = <span class="code-keyword">new</span> Config().getProperties();
+	/** 
+	 * The OSCache 2.0 cache administrator. 
+	 */
+	<span class="code-keyword">private</span> <span class="code-keyword">static</span> GeneralCacheAdministrator cache = <span class="code-keyword">new</span> GeneralCacheAdministrator();
+
+    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-object">Integer</span> capacity = PropertiesHelper.getInteger(OSCACHE_CAPACITY,
+                                                                  OSCACHE_PROPERTIES);
+
+    <span class="code-keyword">static</span> {
+        <span class="code-keyword">if</span> (capacity != <span class="code-keyword">null</span>) cache.setCacheCapacity(capacity.intValue());
+    }
+    
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">int</span> refreshPeriod;
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span> cron;
+	<span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span> regionName;
+    <span class="code-keyword">private</span> <span class="code-keyword">final</span> <span class="code-object">String</span>[] regionGroups;
+	
+	<span class="code-keyword">private</span> <span class="code-object">String</span> toString(<span class="code-object">Object</span> key) {
+		<span class="code-keyword">return</span> <span class="code-object">String</span>.valueOf(key) + <span class="code-quote">"."</span> + regionName;
+	}
+
+	<span class="code-keyword">public</span> OSCache(<span class="code-object">int</span> refreshPeriod, <span class="code-object">String</span> cron, <span class="code-object">String</span> region) {
+		<span class="code-keyword">this</span>.refreshPeriod = refreshPeriod;
+		<span class="code-keyword">this</span>.cron = cron;
+		<span class="code-keyword">this</span>.regionName = region;
+        <span class="code-keyword">this</span>.regionGroups = <span class="code-keyword">new</span> <span class="code-object">String</span>[] {region};
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">Object</span> get(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-keyword">try</span> {
+			<span class="code-keyword">return</span> cache.getFromCache( toString(key), refreshPeriod, cron );
+		}
+		<span class="code-keyword">catch</span> (NeedsRefreshException e) {
+			cache.cancelUpdate( toString(key) );
+			<span class="code-keyword">return</span> <span class="code-keyword">null</span>;
+		}
+	}
+
+	<span class="code-keyword">public</span> void put(<span class="code-object">Object</span> key, <span class="code-object">Object</span> value) <span class="code-keyword">throws</span> CacheException {
+		cache.putInCache( toString(key), value, regionGroups );
+	}
+
+	<span class="code-keyword">public</span> void remove(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		cache.flushEntry( toString(key) );
+	}
+
+	<span class="code-keyword">public</span> void clear() <span class="code-keyword">throws</span> CacheException {
+		cache.flushGroup(regionName);
+	}
+
+	<span class="code-keyword">public</span> void destroy() <span class="code-keyword">throws</span> CacheException {
+		<span class="code-keyword">synchronized</span> (cache) {
+		    cache.destroy();
+        }
+	}
+
+	<span class="code-keyword">public</span> void lock(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-comment">// local cache, so we use synchronization
+</span>	}
+
+	<span class="code-keyword">public</span> void unlock(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-comment">// local cache, so we use synchronization
+</span>	}
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> nextTimestamp() {
+		<span class="code-keyword">return</span> Timestamper.next();
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">int</span> getTimeout() {
+		<span class="code-keyword">return</span> Timestamper.ONE_MS * 60000; <span class="code-comment">//ie. 60 seconds
+</span>	}
+
+	<span class="code-keyword">public</span> Map toMap() {
+		<span class="code-keyword">throw</span> <span class="code-keyword">new</span> UnsupportedOperationException();
+	}    
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> getElementCountOnDisk() {
+		<span class="code-keyword">return</span> -1;
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> getElementCountInMemory() {
+		<span class="code-keyword">return</span> -1;
+	}
+    
+	<span class="code-keyword">public</span> <span class="code-object">long</span> getSizeInMemory() {
+		<span class="code-keyword">return</span> -1;
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">String</span> getRegionName() {
+		<span class="code-keyword">return</span> regionName;
+	}
+
+	<span class="code-keyword">public</span> void update(<span class="code-object">Object</span> key, <span class="code-object">Object</span> value) <span class="code-keyword">throws</span> CacheException {
+		put(key, value);
+	}    
+
+	<span class="code-keyword">public</span> <span class="code-object">Object</span> read(<span class="code-object">Object</span> key) <span class="code-keyword">throws</span> CacheException {
+		<span class="code-keyword">return</span> get(key);
+	}
+}</pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate 3 Cache Provider.html b/docs/wiki/Hibernate 3 Cache Provider.html
new file mode 100644
index 0000000..69f421a
--- /dev/null
+++ b/docs/wiki/Hibernate 3 Cache Provider.html	
@@ -0,0 +1,94 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Hibernate 3 Cache Provider
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>Patched version of OSCacheProvider.java for Hibernate 3.0 - originally created by Mathias Bogaert.</p>
+
+<div class="code" style="border-style: solid; "><div class="codeHeader" style="border-bottom-style: solid; "><b>OSCacheProvider.java</b></div><div class="codeContent">
+<pre class="code-java"><span class="code-keyword">import</span> java.util.Properties;
+<span class="code-keyword">import</span> org.hibernate.util.PropertiesHelper;
+<span class="code-keyword">import</span> org.hibernate.util.StringHelper;
+<span class="code-keyword">import</span> org.hibernate.cache.*;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.CacheEntry;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Config;
+
+/**
+ * Support <span class="code-keyword">for</span> OpenSymphony OSCache. This implementation assumes
+ * that identifiers have well-behaved <tt>toString()</tt> methods.
+ */
+<span class="code-keyword">public</span> class OSCacheProvider <span class="code-keyword">implements</span> CacheProvider {
+
+	/** 
+	 * The <tt>OSCache</tt> refresh period property suffix. 
+	 */
+	<span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_REFRESH_PERIOD = <span class="code-quote">"refresh.period"</span>;
+	/** 
+	 * The <tt>OSCache</tt> CRON expression property suffix. 
+	 */
+	<span class="code-keyword">public</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">String</span> OSCACHE_CRON = <span class="code-quote">"cron"</span>;
+	
+	<span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> Properties OSCACHE_PROPERTIES = <span class="code-keyword">new</span> Config().getProperties();
+
+	/**
+	 * Builds a <span class="code-keyword">new</span> {@link Cache} instance, and gets it's properties from the OSCache {@link Config}
+	 * which reads the properties file (<code>oscache.properties</code>) from the classpath.
+	 * If the file cannot be found or loaded, an the defaults are used.
+	 *
+	 * @param region
+	 * @param properties
+	 * @<span class="code-keyword">return</span>
+	 * @<span class="code-keyword">throws</span> CacheException
+	 */
+	<span class="code-keyword">public</span> Cache buildCache(<span class="code-object">String</span> region, Properties properties) <span class="code-keyword">throws</span> CacheException {
+
+		<span class="code-object">int</span> refreshPeriod = PropertiesHelper.getInt(
+			StringHelper.qualify(region, OSCACHE_REFRESH_PERIOD), 
+			OSCACHE_PROPERTIES, 
+			CacheEntry.INDEFINITE_EXPIRY
+		);
+		<span class="code-object">String</span> cron = OSCACHE_PROPERTIES.getProperty( StringHelper.qualify(region, OSCACHE_CRON) );
+
+		<span class="code-comment">// construct the cache        
+</span>        <span class="code-keyword">return</span> <span class="code-keyword">new</span> OSCache(refreshPeriod, cron, region);
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">long</span> nextTimestamp() {
+		<span class="code-keyword">return</span> Timestamper.next();
+	}
+
+	<span class="code-keyword">public</span> <span class="code-object">boolean</span> isMinimalPutsEnabledByDefault() {
+		<span class="code-keyword">return</span> <span class="code-keyword">false</span>;
+	}
+
+	/**
+	 * Callback to perform any necessary cleanup of the underlying cache implementation
+	 * during SessionFactory.close().
+	 */
+	<span class="code-keyword">public</span> void stop() {
+	}
+
+	/**
+	 * Callback to perform any necessary initialization of the underlying cache implementation
+	 * during SessionFactory construction.
+	 *
+	 * @param properties current configuration settings.
+	 */
+	<span class="code-keyword">public</span> void start(Properties properties) <span class="code-keyword">throws</span> CacheException {
+	}    
+}</pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Hibernate.html b/docs/wiki/Hibernate.html
new file mode 100644
index 0000000..6606fcd
--- /dev/null
+++ b/docs/wiki/Hibernate.html
@@ -0,0 +1,144 @@
+<html>
+<head>
+<title>OSCache - Hibernate</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<p><a href="http://www.hibernate.org/"
+			title="Visit page outside Confluence">Hibernate</a> is a powerful,
+		ultra-high performance object/relational persistence and query service
+		for Java. Hibernate lets you develop persistent objects following
+		common Java idiom - including association, inheritance, polymorphism,
+		composition and the Java collections framework. Extremely
+		fine-grained, richly typed object models are possible.</p>
+
+		<p>Hibernate 3.2 features support for plugin cache providers and
+		is designed to integrate with distributed caches (3.2 also implements
+		more aggressive use of the cache). <b>net.sf.hibernate.cache.CacheProvider</b>
+		is the extension point for user-defined cache integration.</p>
+
+		<p><a href="http://www.hibernate.org/6.html"
+			title="Visit page outside Confluence">Hibernate Core 3.2.3 GA</a> or
+		higher is required.</p>
+
+		<table cellpadding='5' width='85%' cellspacing='8px'
+			class='warningMacro' border="0" align='center'>
+			<colgroup>
+				<col width='24'>
+				<col>
+			</colgroup>
+			<tr>
+				<td valign='top'><img src="./icons/emoticons/forbidden.gif"
+					width="16" height="16" align="absmiddle" alt="" border="0"></td>
+				<td><b class="strong">Warning</b><br />
+				<p>Unfortunately, at this time the OSCacheProvider distributed
+				with Hibernate 3.2 is not suited to clustering with OSCache. A patch
+				has been submitted to the Hibernate team but not yet applied. Even
+				if you do not require clustering, we recommend use of
+				OSCacheProvider and OSCache for Hibernate 3.2 delivered by OSCache
+				2.4 and higher. The OSCache only allows a single cache.capacity
+				setting and saves memory.</p>
+				</td>
+			</tr>
+		</table>
+
+		<h3><a name="Hibernate-hibernate.cache.providerclass"></a>hibernate.cache.provider_class</h3>
+
+		<p>OSCache and Hibernate 3.2 integrate though OSCacheProvider.</p>
+
+		<table cellpadding='5' width='85%' cellspacing='8px' class='infoMacro'
+			border="0" align='center'>
+			<colgroup>
+				<col width='24'>
+				<col>
+			</colgroup>
+			<tr>
+				<td valign='top'><img src="./icons/emoticons/information.gif"
+					width="16" height="16" align="absmiddle" alt="" border="0"></td>
+				<td><b class="strong">Clustering</b><br />
+				<p>The clustered cache invalidation only works for entity
+				updates when usage="nonstrict-read-write".</p>
+				</td>
+			</tr>
+		</table>
+
+		<p>To enable OSCache for Hibernate's second level cache add the
+		following line to Hibernate's configuration e.g. <em>hibernate.cfg.xml</em>:</p>
+
+		<div class="code">
+		<div class="codeHeader"><b>hibernate.cfg.xml</b></div>
+		<div class="codeContent"><pre class="code-xml"><span
+			class="code-tag"><property name=<span class="code-quote">"hibernate.cache.provider_class"</span>></span>com.opensymphony.oscache.hibernate.OSCacheProvider<span
+			class="code-tag"></property></span></pre></div>
+		</div>
+
+		<p>The default refresh period is <a
+			href="http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/base/CacheEntry.html#INDEFINITE_EXPIRY"
+			title="Visit page outside Confluence">CacheEntry.INDEFINITE_EXPIRY</a>.
+		The first time a cacheable query is done, the cache has no effect on
+		speed. On the second and successive queries, the cache will be
+		populated and available to be hit.</p>
+
+		<table cellpadding='5' width='85%' cellspacing='8px' class='noteMacro'
+			border="0" align='center'>
+			<colgroup>
+				<col width='24'>
+				<col>
+			</colgroup>
+			<tr>
+				<td valign='top'><img src="./icons/emoticons/warning.gif"
+					width="16" height="16" align="absmiddle" alt="" border="0"></td>
+				<td><b class="strong">Object Identifiers</b><br />
+				<p>Object identifiers must have well-behaved toString() methods,
+				because OSCache uses the toString() method for the key of the cache.
+				Therefore it needs to create a unique identifier for the object
+				being stored.</p>
+				</td>
+			</tr>
+		</table>
+
+		<h3><a name="Hibernate-CacheRegionConfiguration"></a>Cache Region
+		Configuration</h3>
+
+		<p>To modify the refresh period, CRON expression, add the region
+		configuration to your <a href="Configuration.html"
+			title="Configuration">oscache.properties</a> file, as demonstrated
+		below:</p>
+
+		<div class="preformatted">
+		<div class="preformattedContent"><pre>[region].refresh.period = 4000
+[region].cron = * * 31 Feb *
+</pre></div>
+		</div>
+
+		<p>The com.mypackage.domain.Customer is Hibernate's internal cache
+		region, which defaults to the classname, and which can be altered by
+		setting Hibernate's configuration property <a
+			href="http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html"
+			title="Visit page outside Confluence">hibernate.cache.region_prefix</a>
+		.</p>
+
+		<h3><a
+			name="Hibernate-ConfigureadifferentconfigurationfileforHibernate"></a>Configure
+		a different configuration file for Hibernate</h3>
+
+		<p>To configure a different configuration file use the following
+		parameter in the Hibernate's configuration:</p>
+		<div class="code">
+		<div class="codeHeader"><b>hibernate.cfg.xml</b></div>
+		<div class="codeContent"><pre class="code-xml"><span
+			class="code-tag"><property name=<span class="code-quote">"com.opensymphony.oscache.configurationResourceName"</span>></span>path to oscache-hibernate.properties<span
+			class="code-tag"></property></span></pre></div>
+		</div>
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/Home.html b/docs/wiki/Home.html
new file mode 100644
index 0000000..95995c9
--- /dev/null
+++ b/docs/wiki/Home.html
@@ -0,0 +1,72 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Home
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h1><a name="Home-WelcometotheOSCachewiki."></a>Welcome to the OSCache wiki. </h1>
+
+<p>OSCache is a caching solution that includes a JSP tag library and set of classes to perform fine grained dynamic caching of JSP content, servlet responses or arbitrary objects. It provides both in memory and persistent on disk caches, and can allow your site to have graceful error tolerance (eg if an error occurs like your db goes down, you can serve the cached content so people can still surf the site almost without knowing). Take a look at the great <a href="Feature List.html" title [...]
+
+<p>This wiki is used for additional information as well as documentation for the latest developing version (see <a href="Change Log.html" title="Change Log">previous releases</a>).</p>
+
+<ul>
+	<li><a href="Documentation.html" title="Documentation">Documentation</a>
+	<ul>
+		<li><a href="Installation Guide.html" title="Installation Guide">Installation Guide</a> - How to install OSCache and where to get it.</li>
+		<li><a href="Configuration.html" title="Configuration">Configuration</a> - Configuration options for OSCache.</li>
+		<li><a href="http://www.opensymphony.com/oscache/api/" title="Visit page outside Confluence">JavaDoc API</a> - The OSCache API documentation in JavaDoc format.</li>
+	</ul>
+	</li>
+</ul>
+
+
+
+<ul>
+	<li><a href="https://oscache.dev.java.net/servlets/ProjectDocumentList" title="Visit page outside Confluence">Download</a> - Download Source and Binaries.</li>
+</ul>
+
+
+
+<ul>
+	<li>Versions
+	<ul>
+		<li><a href="Change Log.html" title="Change Log">Change Log</a> - See what's new in the latest version of OSCache.</li>
+		<li><a href="Roadmap.html" title="Roadmap">Roadmap</a> - See the expected future releases of OSCache.</li>
+	</ul>
+	</li>
+</ul>
+
+
+
+<ul>
+	<li>Contributing
+	<ul>
+		<li><a href="http://forums.opensymphony.com/forum.jspa?forumID=4" title="Visit page outside Confluence">Support Forum</a></li>
+		<li><a href="https://oscache.dev.java.net/servlets/ProjectMailingListList" title="Visit page outside Confluence">Mailing List</a></li>
+		<li><a href="http://svn.opensymphony.com/svn/oscache/" title="Visit page outside Confluence">SVN Access</a> beginning with release 2.4 or <a href="https://oscache.dev.java.net/source/browse/oscache/" title="Visit page outside Confluence">CVS Access</a> until release 2.3.2</li>
+	</ul>
+	</li>
+</ul>
+
+
+
+<ul>
+	<li><a href="Index.html" title="Index">Index</a></li>
+</ul>
+
+
+<p>OSCache's official homepage is <a href="http://www.opensymphony.com/oscache/" title="Visit page outside Confluence">http://www.opensymphony.com/oscache/</a>. There you can find the documentation of the latest production release of OSCache.</p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Installation Guide.html b/docs/wiki/Installation Guide.html
new file mode 100644
index 0000000..807c1e0
--- /dev/null
+++ b/docs/wiki/Installation Guide.html	
@@ -0,0 +1,145 @@
+<html>
+<head>
+<title>OSCache - Installation Guide</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<p>This installation guide shows you how to configure OSCache 2.4
+		for use inside your JSP pages. It assumes you have <a
+			href="https://oscache.dev.java.net/servlets/ProjectDocumentList"
+			title="Visit page outside Confluence">downloaded the latest
+		version</a>, which requires at least <b>Java 1.4</b> and a <b>Servlet
+		2.3</b> container (part of J2EE 1.3). Read the <a href="Requirements.html"
+			title="Requirements">Requirements</a> for more details.</p>
+
+		<p>If you intend to use OSCache via the <a href="API Usage.html"
+			title="API Usage">API</a> rather than via the <a href="JSP Tags.html"
+			title="JSP Tags">taglibs</a>, these instructions do not apply. Just
+		make sure <tt>oscache.jar</tt> and <tt>commons-logging.jar</tt> are
+		somewhere on your application's classpath.</p>
+
+		<h4><a name="InstallationGuide-ExtractionSteps"></a>Extraction
+		Steps</h4>
+
+		<ol>
+			<li>Extract the downloaded file to a directory of your choosing.</li>
+			<li>Put the oscache.jar file in the <tt>/WEB-INF/lib</tt>
+			directory</li>
+			<li>Make sure <tt>commons-logging.jar</tt> is on your classpath
+			(normally this also means putting it in <tt>/WEB-INF/lib</tt>).</li>
+			<li>Put the <tt>/etc/oscache.properties</tt> file in the <tt>/WEB-INF/classes</tt>
+			directory and edit the properties contained within it (for example if
+			you want disk caching, configure the persistence listener and edit
+			the <tt>cache.path</tt> property to point to where you want the cache
+			files stored on disk). See the <a href="configuration.html"
+				title="Configuration">Configuration Guide</a> for further details on
+			what options are available.</li>
+			<li>Your directory structure should now look something like
+			this:
+			<div class="panel">
+			<div class="panelContent">
+			<p>$WEB_APPLICATION/WEB-INF/lib/oscache.jar<br />
+			$WEB_APPLICATION/WEB-INF/classes/oscache.properties</p>
+			</div>
+			</div>
+			</li>
+		</ol>
+
+
+		<table cellpadding='5' width='85%' cellspacing='8px' class='infoMacro'
+			border="0" align='center'>
+			<colgroup>
+				<col width='24'>
+				<col>
+			</colgroup>
+			<tr>
+				<td valign='top'><img src="./icons/emoticons/information.gif"
+					width="16" height="16" align="absmiddle" alt="" border="0"></td>
+				<td><b class="strong">Windows</b><br />
+				<p>Remember to escape any \ characters in Windows paths - e.g.
+				if you want cache files to go in c:\cachedir, the <tt>cache.path</tt>
+				property should be set to c:\ \cachedir.</p>
+				</td>
+			</tr>
+		</table>
+
+		<h4><a name="InstallationGuide-InstallationSteps"></a>Installation
+		Steps</h4>
+
+		<ol>
+			<li>Now add the appropriate <a href="JSP Tags.html"
+				title="JSP Tags">JSP Tags</a> to your JSP files and you're done.</li>
+			<li>It should work properly. <a
+				href="mailto:users at oscache.dev.java.net"
+				title="Send mail to Tell us on the mailing list">Tell us on the
+			mailing list</a> if it doesn't work in your container.</li>
+		</ol>
+
+
+		<h4><a name="InstallationGuide-FurtherInformation"></a>Further
+		Information</h4>
+
+		<table class="sectionMacro" border="0" cellpadding="5" cellspacing="0"
+			width="100%">
+			<tbody>
+				<tr>
+
+					<td class="confluenceTd" valign="top" width="50%">
+
+					<table cellpadding='5' width='85%' cellspacing='8px'
+						class='infoMacro' border="0" align='center'>
+						<colgroup>
+							<col width='24'>
+							<col>
+						</colgroup>
+						<tr>
+							<td valign='top'><img
+								src="./icons/emoticons/information.gif" width="16" height="16"
+								align="absmiddle" alt="" border="0"></td>
+							<td><b class="strong">Logging</b><br />
+							<p>OSCache uses <a
+								href="http://jakarta.apache.org/commons/logging/"
+								title="Visit page outside Confluence">Jakarta Commons
+							Logging</a> for logging any messages. Please see the <a
+								href="http://jakarta.apache.org/commons/logging/guide.html#Configuration"
+								title="Visit page outside Confluence">Commons Logging
+							documentation</a> for details on logging configuration.</p>
+							</td>
+						</tr>
+					</table>
+					</td>
+
+					<td class="confluenceTd" valign="top" width="50%">
+
+					<table cellpadding='5' width='85%' cellspacing='8px'
+						class='tipMacro' border="0" align='center'>
+						<colgroup>
+							<col width='24'>
+							<col>
+						</colgroup>
+						<tr>
+							<td valign='top'><img src="./icons/emoticons/check.gif"
+								width="16" height="16" align="absmiddle" alt="" border="0"></td>
+							<td><b class="strong">Debugging</b><br />
+							<p>Note that OSCache has been compiled with debugging
+							information enabled so you should be able to use your favourite
+							debugger to step through the source if need be.</p>
+							</td>
+						</tr>
+					</table>
+					</td>
+				</tr>
+			</tbody>
+		</table>
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/JMX Monitoring.html b/docs/wiki/JMX Monitoring.html
new file mode 100644
index 0000000..4980b26
--- /dev/null
+++ b/docs/wiki/JMX Monitoring.html	
@@ -0,0 +1,68 @@
+<html>
+    <head>
+        <title>OSCache - 
+         Monitoring
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>New in OSCache 2.4 is support for JMX monitoring and administration via the <a href="http://www.springframework.org" title="Visit page outside Confluence">Spring Framework</a>.</p>
+
+<p>In oscache.properties, enable the statistic listener:</p>
+
+<div class="preformatted"><div class="preformattedContent">
+<pre>cache.event.listeners= com.opensymphony.oscache.extra.StatisticListenerImpl
+</pre>
+</div></div>
+
+<p>Then add this to the Spring application context</p>
+
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><!-- create mbeanserver, this doesn't need to be done if running on an Appserver with 
+it's own JMX server, such as Tomcat -->
+<span class="code-tag"><bean id=<span class="code-quote">"mbeanServer"</span> class=<span class="code-quote">"org.springframework.jmx.support.MBeanServerFactoryBean"</span>/></span>
+
+<span class="code-tag"><span class="code-comment"><!-- create a connector on port 1109 --></span></span>
+<bean id=<span class="code-quote">"registry"</span>
+	class=<span class="code-quote">"org.springframework.remoting.rmi.RmiRegistryFactoryBean"</span>>
+	<span class="code-tag"><property name=<span class="code-quote">"port"</span>></span>
+		<span class="code-tag"><value></span>1109<span class="code-tag"></value></span>
+	<span class="code-tag"></property></span>
+<span class="code-tag"></bean></span>
+
+<bean id=<span class="code-quote">"serverConnector"</span> depends-on=<span class="code-quote">"registry"</span>
+	class=<span class="code-quote">"org.springframework.jmx.support.ConnectorServerFactoryBean"</span>>
+	<span class="code-tag"><property name=<span class="code-quote">"objectName"</span>></span>
+		<span class="code-tag"><value></span>connector:name=rmi<span class="code-tag"></value></span>
+	<span class="code-tag"></property></span>
+	<span class="code-tag"><property name=<span class="code-quote">"serviceUrl"</span>></span>
+		<span class="code-tag"><value></span>service:jmx:rmi://localhost/jndi/rmi://localhost:1109/jmxconnector<span class="code-tag"></value></span>
+	<span class="code-tag"></property></span>			
+<span class="code-tag"></bean></span>  
+
+<span class="code-tag"><span class="code-comment"><!-- export the oscache stats beans --></span></span>
+<bean id=<span class="code-quote">"exporter"</span>
+      class=<span class="code-quote">"org.springframework.jmx.export.MBeanExporter"</span>>
+	<span class="code-tag"><property name=<span class="code-quote">"beans"</span>></span>
+		<span class="code-tag"><map></span>   
+                <span class="code-tag"><entry key=<span class="code-quote">"bean:name=StatisticListenerImpl"</span>></span>
+				<span class="code-tag"><value></span>StatisticListenerImpl<span class="code-tag"></value></span>
+		<span class="code-tag"></entry></span>	                
+		<span class="code-tag"></map></span>
+	<span class="code-tag"></property></span>
+<span class="code-tag"></bean></span>
+
+<span class="code-tag"><span class="code-comment"><!-- oscache stats bean --></span></span>
+<span class="code-tag"><bean id=<span class="code-quote">"StatisticListenerImpl"</span> class=<span class="code-quote">"com.opensymphony.oscache.extra.StatisticListenerImpl"</span>/></span></pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/JSP Tags.html b/docs/wiki/JSP Tags.html
new file mode 100644
index 0000000..fd97c8c
--- /dev/null
+++ b/docs/wiki/JSP Tags.html	
@@ -0,0 +1,230 @@
+<html>
+    <head>
+        <title>OSCache - 
+         Tags
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p><b>OSCache</b> comes with a JSP tag library that controls all its major functions. The tags are listed below with descriptions, attributes and examples of use.</p>
+
+<p>For instructions on installing OSCache in a web application, see the <a href="Installation Guide.html" title="Installation Guide">Installation Guide</a>. You just have to add the following line declaring the OSCache custom tag library for use on the jsp page:</p>
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='tipMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/check.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Taglib URI</b><br />
+<p><tt><%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %></tt></p>
+
+<p>In OSCache releases before 2.1.1 you have to change the URI to <tt>/oscache</tt>, see <a href="http://jira.opensymphony.com/browse/CACHE-61" title="Visit page outside Confluence">CACHE-61</a>.</p></td></tr></table>
+
+<h3><a name="JSPTags-Summary"></a>Summary</h3>
+
+<p>The tags are:</p>
+
+<ul>
+	<li><a href="#JSPTags-cache" title="cache on JSP Tags">cache</a> - The main caching tag</li>
+	<li><a href="#JSPTags-usecached" title="usecached on JSP Tags">usecached</a> - A nested tag to force using a cached version.</li>
+	<li><a href="#JSPTags-flush" title="flush on JSP Tags">flush</a> - To flush caches programmatically.</li>
+	<li><a href="#JSPTags-addgroup" title="addgroup on JSP Tags">addgroup</a> - It allows a single group name to be dynamically added to a cached block. This tag must be nested inside <cache:cache/>.</li>
+	<li><a href="#JSPTags-addgroups" title="addgroups on JSP Tags">addgroups</a> - It allows a comma-delimited list of group names to be dynamically added to a cached block. This tag must be nested inside <cache:cache/>.</li>
+</ul>
+
+
+<table cellpadding='5' width='85%' cellspacing='8px' class='infoMacro' border="0" align='center'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="./icons/emoticons/information.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b class="strong">Tag Legend</b><br />
+<ul>
+	<li>For all listed attributes,  <font color="red">req</font> means it that attribute is required and any value in <b>[ ]</b> is a default value. All attributes can accept runtime expressions.</li>
+</ul>
+
+
+<ul>
+	<li>From the title of the tag you can see whether or not the tag has a body:
+	<ul>
+		<li><b><tag></tag></b> tags always have a body</li>
+		<li><b><tag /></b> does not have a body</li>
+		<li><b><tag /></tag></b> can have a body or not depending on the circumstances.</li>
+	</ul>
+	</li>
+</ul>
+</td></tr></table>
+
+
+<h3><a name="JSPTags-%3Ccache%3E%3C%2Fcache%3E"></a><a name="JSPTags-cache"></a><cache></cache></h3>
+
+<h4><a name="JSPTags-Description%3A"></a>Description:</h4>
+
+<p>    This is the main tag of OSCache. The body of the tag will be cached according to the attributes specified. The first time a cache is used the body content is executed and cached.</p>
+
+<p>    Each subsequent time the tag is run, it will check to see if the cached content is stale. Content is considered stale due to one (or more) of the following being true:</p>
+
+<ul>
+	<li>The cached body content has been in the cache for longer than the time specified by the time or duration attribute.</li>
+	<li>The cron attribute matches a date/time that is more recent than the time the body content was originally cached.</li>
+	<li>The scope the body content is cached in was flushed since the content was originally cached.</li>
+</ul>
+
+
+<p>    If the cached body content is stale, the tag will execute the body again and recache the new body content. Otherwise it will serve the cached content and the body will be skipped (resulting in a large speed increase).</p>
+
+<h4><a name="JSPTags-Attributes%3A"></a>Attributes:</h4>
+
+<ul>
+	<li><b>key</b> - [The request URI + query string] - The cache key, any string. This should be unique for the given scope since duplicate keys will map to the same cache entry. The default value uses an escaped version of the URI and query string of the current page.<br/>
+      It is possible to specify multiple cache tags in the same page without specifying keys - in this situation an index is appended to the key of subsequent tags. However this usage is discouraged since if the flow of the page is inconsistent, or cache tags are nested, the indicies will potentially change each time the page is executed, resulting in seemingly jumbled cache entries.</li>
+	<li><b>scope</b> - [application] - The scope of this cache (valid values are "application" and "session").</li>
+	<li><b>time</b> - [3600] The amount of time to cache this content for (in seconds). (Default is 3600 seconds, one hour). Supplying a negative value for this attribute means that the content never expires.</li>
+	<li><b>duration</b> - [] - The duration of this cache (this attribute is an alternative to time). duration can be specified using Simple Date Format or ISO-8601 date format.</li>
+	<li><b>cron</b> - [] - A cron expression that determines when this cached content will expire. This allows content to be expired at particular dates and/or times, rather than once a cache entry reaches a certain age. See <a href="Cron Expressions.html" title="Cron Expressions">Cron Expressions</a> to read more about this attribute.</li>
+	<li><b>refresh</b> - [false] - A boolean. If true, the cache will be refreshed regardless of whether it is considered stale or not. This enables you to decide at runtime whether or not to rebuild the content.</li>
+	<li><b>mode</b> - [] - Setting this to "silent" will prevent the body of the tag from being written to the output stream. This may be useful if you want to preload the cache with content without actually displaying that content to the user.</li>
+	<li><b>groups</b> - [] - A comma-delimited list of group names can be provided. This allows cache entries to be grouped according to your needs. Grouping is useful when you have cached content that depends on other parts of your application or data - when that dependency changes, flushing the relevant group will cause all cache entries in that group to be expired.</li>
+	<li><b>language</b> - [] - The ISO-639 language code to distinguish different content cached under an otherwise identical key. This is useful on a multilingual site where the same JSP code is used to render content in different languages depending on the current user's preferences.</li>
+	<li><b>refreshpolicyclass</b> - [] - A fully-qualified classname that extends com.opensymphony.oscache.web.WebEntryRefreshPolicy. This allows you to programmatically determine whether cached content should be exipired.</li>
+	<li><b>refreshpolicyparam</b> - [] - Any arbitrary parameters that you need to pass through to the refreshpolicyclass. Specifying this attribute without specifying a refreshpolicyclass will have no effect.
+<div class="code"><div class="codeHeader"><b>Examples</b></div><div class="codeContent">
+<pre class="code-xml">This will cache the JSP content using the current URI as a key (which means this must be the only cache tag on the page to work).
+
+    <span class="code-tag"><cache:cache></span>
+         ... some jsp content ...
+    <span class="code-tag"></cache:cache></span>
+
+    This will cache the content with a constant key in the user's session scope. Any page that uses this key will access one shared cache.
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"foobar"</span> scope=<span class="code-quote">"session"</span>></span>
+         ... some jsp content ...
+    <span class="code-tag"></cache:cache></span>
+
+    This will cache the content with a programmatic key (here a product ID) for 30 minutes. It will also refresh if the variable needRefresh 
+is true.
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"<%= product.getId() %></span>"</span> time=<span class="code-quote">"1800"</span> refresh=<span class="code-quote">"<span class="code-tag"><%= needRefresh %></span>"</span>>
+         ... some jsp content ...
+    <span class="code-tag"></cache:cache></span>
+
+    This will cache the content with a programmatic key, expiring it every morning at 2am. It will also refresh if the variable needRefresh 
+is true.
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"<%= product.getId() %></span>"</span> cron=<span class="code-quote">"0 2 * * *"</span> refresh=<span class="code-quote">"<span class="code-tag"><%= needRefresh %></span>"</span>>
+         ... some jsp content ...
+    <span class="code-tag"></cache:cache></span>
+
+    Suppose we had a dynamic list of categories that we pull from a database, and we also store currency exchange rates that get updated 
+occasionally by calling a webservice. Suppose also that we have some content that displays information about both the categories and the 
+current exchange rate values. The following example caches the body content and assigns it to two cache groups, <span class="code-quote">"currencyData"</span> and 
+<span class="code-quote">"categoryList"</span>. When the exchange rates or the category list is updated, the appropriate group can be flushed causing this content (along 
+with any other content associated with that group) to be exipired and then rebuilt the next time the page is processed:
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"<%= product.getId() %></span>"</span> time=<span class="code-quote">"-1"</span> group=<span class="code-quote">"currencyData, categories"</span>>
+         ... display category list ...
+         ... display currency information ...
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div></li>
+</ul>
+
+
+<h3><a name="JSPTags-%3Cusecached%2F%3E"></a><a name="JSPTags-usecached"></a><usecached /></h3>
+<h4><a name="JSPTags-Description%3A"></a>Description:</h4>
+<p>This tag is nested within a <cache> tag and tells its parent whether or not to use the cached version.</p>
+
+<h4><a name="JSPTags-Attributes%3A"></a>Attributes:</h4>
+<ul>
+	<li><b>use</b> - [true] - A boolean that tells the tag whether or not to use the cached version. (true = use cached version). This is useful for<br/>
+programmatic control of the cache.
+<div class="code"><div class="codeHeader"><b>Example</b></div><div class="codeContent">
+<pre class="code-xml">This is a good example of error tolerance. If an exception occurs, the cached version of this content will be output instead.
+
+    <span class="code-tag"><cache:cache></span>
+         <span class="code-tag"><% try { %></span>
+         ... some jsp content ...
+         <span class="code-tag"><% } catch (Exception e) { %></span>
+              <span class="code-tag"><cache:usecached /></span>
+         <span class="code-tag"><% } %></span>
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div></li>
+</ul>
+
+
+<h3><a name="JSPTags-%3Cflush%2F%3E"></a><a name="JSPTags-flush"></a><flush /></h3>
+
+<h4><a name="JSPTags-Description%3A"></a>Description:</h4>
+<p>    This tag is used to flush caches at runtime. It is especially useful because it can be coded into the administration section of your site so that admins can decide when to flush the caches.</p>
+
+<h4><a name="JSPTags-Attributes%3A"></a>Attributes:</h4>
+
+<ul>
+	<li><b>scope</b> - [all] - This decides what scope will be flushed. Valid values are "application", "session" and null. A null scope will flush all caches, regardless of their scope.</li>
+	<li><b>key</b> - [] - When a key and a scope are both given, just that single cache entry will be marked to be flushed. When it is next accessed, it will be refreshed. It is not valid to specify a key without a scope.</li>
+	<li><b>group</b> - [] - Specifying a group will cause all cache entries in the group to be flushed. It is not valid to specify a group without a scope.</li>
+	<li><b>pattern</b> - [] - Any keys that contain this string will be flushed from the specified scope. It is not valid to specify a pattern without a scope. (Note: pattern flushing has been deprecated - you are encouraged to use the grouping functionality instead. It is more flexible and provides better performance.)</li>
+	<li><b>language</b> - [] - The ISO-639 language code to distinguish different content cached under an otherwise identical key. This is useful on a multilingual site where the same JSP code is used to render content in different languages depending on the current user's preferences.</li>
+</ul>
+
+
+<div class="code"><div class="codeHeader"><b>Example</b></div><div class="codeContent">
+<pre class="code-xml">This will flush the application scope.
+
+    <span class="code-tag"><cache:flush scope=<span class="code-quote">"application"</span> /></span>
+
+    This will flush the cache entry with key <span class="code-quote">"foobar"</span> in the session scope.
+
+    <span class="code-tag"><cache:flush scope=<span class="code-quote">"session"</span> key=<span class="code-quote">"foobar"</span> /></span>
+
+    This will flush all cache entries in the <span class="code-quote">"currencyData"</span> group from the application scope.
+
+    <span class="code-tag"><cache:flush scope=<span class="code-quote">"application"</span> group=<span class="code-quote">"currencyData"</span> /></span></pre>
+</div></div>
+
+<h3><a name="JSPTags-%3Caddgroup%2F%3E"></a><a name="JSPTags-addgroup"></a><addgroup /></h3>
+
+<h4><a name="JSPTags-Description%3A"></a>Description:</h4>
+
+<p>    This tag must be nested inside a <cache:cache/> tag. It allows a single group name to be dynamically added to a cached block. It is useful when the group a cached block should belong to are unknown until the block is actually rendered. As each group is 'discovered', this tag can be used to add the group to the block's group list.</p>
+
+<h4><a name="JSPTags-Attributes%3A"></a>Attributes:</h4>
+
+<ul>
+	<li><b>group</b> - <font color="red">req</font> - The name of the group to add the enclosing cache block to.</li>
+</ul>
+
+
+<div class="code"><div class="codeHeader"><b>Example</b></div><div class="codeContent">
+<pre class="code-xml">This will add the cache block with the key 'test1' to groups 'group1' and 'group2'.
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"test1"</span>></span>
+         <span class="code-tag"><cache:addgroup group=<span class="code-quote">"group1"</span> /></span>
+         ... some jsp content ...
+         <span class="code-tag"><cache:addgroup group=<span class="code-quote">"group2"</span> /></span>
+         ... some more jsp content ...
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div>
+
+<h3><a name="JSPTags-%3Caddgroups%2F%3E%28New%5C%21Since2.3%29"></a><a name="JSPTags-addgroups"></a><addgroups /> (New! Since 2.3)</h3>
+
+<h4><a name="JSPTags-Description%3A"></a>Description:</h4>
+
+<p>    This tag must be nested inside a <cache:cache/> tag. It allows a comma-delimited list of groups names to be dynamically added to a cached block with a single tag statement. As a group list is 'discovered', this tag can be used to add the groups to the block's group list.</p>
+
+<h4><a name="JSPTags-Attributes%3A"></a>Attributes:</h4>
+
+<ul>
+	<li><b>groups</b> - <font color="red">req</font> - The comma-delimited list of groups names to add the enclosing cache block to.</li>
+</ul>
+
+
+<div class="code"><div class="codeHeader"><b>Example</b></div><div class="codeContent">
+<pre class="code-xml">This will add the cache block with the key 'test1' to groups 'group1' and 'group2'.
+
+    <span class="code-tag"><cache:cache key=<span class="code-quote">"test1"</span>></span>
+         ... some jsp content ...
+         <span class="code-tag"><cache:addgroups groups=<span class="code-quote">"group1,group2"</span> /></span>
+         ... some jsp content ...
+    <span class="code-tag"></cache:cache></span></pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/License.html b/docs/wiki/License.html
new file mode 100644
index 0000000..66b4bbe
--- /dev/null
+++ b/docs/wiki/License.html
@@ -0,0 +1,20 @@
+<html>
+    <head>
+        <title>OSCache - 
+        License
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>All OpenSymphony projects use the OpenSymphony License, which is a modified Apache License. You can find the license at <a href="http://www.opensymphony.com/oscache/license.action" title="Visit page outside Confluence">http://www.opensymphony.com/oscache/license.action</a></p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.0 beta 0.html b/docs/wiki/OSCache 1.0 beta 0.html
new file mode 100644
index 0000000..b5515c9
--- /dev/null
+++ b/docs/wiki/OSCache 1.0 beta 0.html	
@@ -0,0 +1,29 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.0 beta 0
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.0beta0-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(26th November, 2000 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Initial release of OSCache</li>
+	<li>Conceptualised a few things I've been working on over the past month.</li>
+	<li>Added persistent on disk caching and error tolerance (through <usecached /> tag)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.0 beta 1.html b/docs/wiki/OSCache 1.0 beta 1.html
new file mode 100644
index 0000000..91e96dc
--- /dev/null
+++ b/docs/wiki/OSCache 1.0 beta 1.html	
@@ -0,0 +1,30 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.0 beta 1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.0beta1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(20th February, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Fixed a few bugs.</li>
+	<li>Greatest of which there is no longer a disk leakage from session caches on disk.</li>
+	<li>Also session caching bugs fixed, usecached bugs fixed - lots of work done here.</li>
+	<li>Implemented to flush individual keys.</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.0 beta 2.html b/docs/wiki/OSCache 1.0 beta 2.html
new file mode 100644
index 0000000..bb70e9d
--- /dev/null
+++ b/docs/wiki/OSCache 1.0 beta 2.html	
@@ -0,0 +1,29 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.0 beta 2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.0beta2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(20th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Fixed more bugs.</li>
+	<li>Moved things around so that the CacheAdministrator has more functionality and is now a Singleton (per web app context). This means no more depedency on ServletContextListener to start the CacheAdministrator.</li>
+	<li>Therefore we are now Servlet 2.2 / JSP 1.1 compliant! w00!</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.1.html b/docs/wiki/OSCache 1.1.html
new file mode 100644
index 0000000..fa70d44
--- /dev/null
+++ b/docs/wiki/OSCache 1.1.html	
@@ -0,0 +1,31 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(25th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Moved up to 1.1 because a lot of documentation improving and some small bug fixing has been done</li>
+	<li>Javadocs should now be very readable for all classes and methods</li>
+	<li>Fixed a NullPointer that was being thrown in CacheEntry.needsRefresh()</li>
+	<li>Cleaned up the build file so it now produces releasable zip files easily</li>
+	<li>Added servlet.jar so that the compiling now works OOB (Out Of the Box)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.2.1.html b/docs/wiki/OSCache 1.2.1.html
new file mode 100644
index 0000000..f09a164
--- /dev/null
+++ b/docs/wiki/OSCache 1.2.1.html	
@@ -0,0 +1,29 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.2.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.2.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(10th May, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Speed improvements in CacheEntry and CacheAdministrator (by Kesav Kumar - kesavk at voquette.com)</li>
+	<li>Fixed DOCTYPE in taglib.tld (also Kesav!)</li>
+	<li>Removed backup / swap / temp files from zip (and changed build file)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.2.5.html b/docs/wiki/OSCache 1.2.5.html
new file mode 100644
index 0000000..748e26b
--- /dev/null
+++ b/docs/wiki/OSCache 1.2.5.html	
@@ -0,0 +1,28 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.2.5
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.2.5-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(18th May, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Added ability to turn off file caching (just remove or comment out cache.properties)</li>
+	<li>Removed a pesky (but ineffectual) bug where session caches being removed from disk were throwing NullPointerExceptions</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.2.html b/docs/wiki/OSCache 1.2.html
new file mode 100644
index 0000000..9444cfa
--- /dev/null
+++ b/docs/wiki/OSCache 1.2.html	
@@ -0,0 +1,27 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(28th March, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Fixed a large bug that resulted in CacheEntry's not refreshing. Large enough in a Caching library to demand a new point release <img class="emoticon" src="./icons/emoticons/wink.gif" height="20" width="20" align="absmiddle" alt="" border="0"/></li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.3.html b/docs/wiki/OSCache 1.3.html
new file mode 100644
index 0000000..47197fb
--- /dev/null
+++ b/docs/wiki/OSCache 1.3.html	
@@ -0,0 +1,30 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.3
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.3-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(9th June, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Fixed a single bug in the file caching - should now work</li>
+	<li>Added property to set the cache key (not sure if this is useful)</li>
+	<li>Cleaned up a lot of the code, refactored slightly so that the tags are more light weight and rely more on the Administrator and CacheHashMap for functionality.</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.5.html b/docs/wiki/OSCache 1.5.html
new file mode 100644
index 0000000..40d0288
--- /dev/null
+++ b/docs/wiki/OSCache 1.5.html	
@@ -0,0 +1,34 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.5
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.5-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(6th August, 2001 - by Todd Gochenour, tgochenour at peregrine.com)</p>
+
+<ul>
+	<li>Added boolean "cache.memory" attribute to oscache.properties to eliminate memory consumption and rely strictly on disk storage.</li>
+	<li>Added three interfaces "CacheLog", "CacheProperties", and "CacheContents" to allow plugable implementations for these functions. The CacheContents interface allows the pages to be cached using a database.</li>
+	<li>Added "Language" attribute to CacheTag and FlushTag to distinguish a page that supports I18N generation. The ISO-639 language code is used when the scope of the page is "Application". The code defines a subdirectory under the "application" directory of file caching.</li>
+	<li>Modified the CacheAdministrator.generateKey() function to append the request's QueryString to the URI when automatically generating keys. The QueryString is encoded using the MD5 digest base64 algorithms.</li>
+	<li>Added attribute "encoding" to a CacheTag so that the file IO does proper conversion when reading and writing the cache files. (per suggestion of Pedro Gomez)</li>
+	<li>Added retries when SecurityException is thrown. Java has no built in exclusive file locking implementations. The file is written to a lock file and then renamed as an atomic operation so that multiple processes on the same box can reliable access cache data.</li>
+	<li>Added "pattern" attribute to FlushTag which invokes a CacheHashMap.flushPattern() function to scan for and flush all keys that contain the value of the pattern. (per suggestion of Todd Rudrick)</li>
+	<li>Added support for a CacheTag time value of zero which turns off caching for that tag. (per suggestion of Pedro Gomez)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.6.1.html b/docs/wiki/OSCache 1.6.1.html
new file mode 100644
index 0000000..39e7475
--- /dev/null
+++ b/docs/wiki/OSCache 1.6.1.html	
@@ -0,0 +1,32 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.6.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.6.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(16th September, 2001 - by Todd Gochenour, tgochenour at peregrine.com)</p>
+
+<ul>
+	<li>Removed attribute "encoding" in all areas, since Object serialization stores strings in UTF-8 format, encoding is no longer necessary.</li>
+	<li>Added Synchronization to getCacheEntry() to insure multiple threads do not access HashMap and get erroneous results.</li>
+	<li>Implemented property cache.useHostDomainInKey (true/false) to prepend URL request server name to cache key when cache used by multiple servers. The "cache.domainname" property found in oscache.properties (not used in code) was removed.</li>
+	<li>Corrected file caching logic so that multiple processes can share cache information (file locking). Missing still is the ability to signal processes that a cache needs to be flushed when using Memory Caching along with File Caching.</li>
+	<li>Unit testing revealed some minor configuration bugs which were corrected.</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.6.html b/docs/wiki/OSCache 1.6.html
new file mode 100644
index 0000000..9cf38f2
--- /dev/null
+++ b/docs/wiki/OSCache 1.6.html	
@@ -0,0 +1,31 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.6
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.6-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(5th September, 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Changed the CacheEntry so that it caches Object rather than String (allowing image caching) (Serge Knystautas, sergek at lokitech.com)</li>
+	<li>Cached objects are now serialized to disk so cannot be read by humans anymore (this allows us to cache Object) (Serge Knystautas, sergek at lokitech.com)</li>
+	<li>Added a Servlet 2.3 CacheFilter (and associated response classes) that caches whole requests (Serge Knystautas, sergek at lokitech.com)</li>
+	<li>Minor changes to CacheAdministrator (the way Cache and CacheEntry's are retrieved) - merging Serge and Todd's changes</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.0.html b/docs/wiki/OSCache 1.7.0.html
new file mode 100644
index 0000000..da04d37
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.0.html	
@@ -0,0 +1,56 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.0
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.0-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(26th September 2001 - by Francois Beauregard, fbeauregard at pyxis-tech.com, and<br/>
+Alain Bergevin, abergevin at pyxis-tech.com, of Pyxis Technologies Inc.)</p>
+
+<p>This version include some refactoring, corrections and new features.<br/>
+Here are the highlights:</p>
+
+<ul>
+	<li>CacheAdministrator has been split in 3. We have now AbstractCacheAdministrator, and ServletCacheAdministrator and GeneralCacheAdministrator extends it</li>
+	<li>Packages have been adjusted. We now have oscache.base, oscache.general and oscache.Servlet.<br/>
+      Adjustement must be made to the oscache.tld</li>
+	<li>ServletCacheHashMap has been created in order to reflect specific needs for Servlets. It extends CacheHashMap</li>
+	<li>Support for multiple cache tag in a single page, without supplying a key. Nested cache tag are not yet supported (you need to manage keys in that case).</li>
+	<li>OSCache can now cache any objects (not only JSP content) using GeneralCacheAdministrator</li>
+	<li>GenerateKey now support suffixes (used to deal with multiple cache tags)</li>
+	<li>A complete JUnit test suite has been created for osCache, including a JSP and a Servlet</li>
+	<li>Added the required libraries for the test unit. The JUnit JAR has been upgraded to version 3.7</li>
+	<li>Required libraries are now HHTPUnit, Tidy, JUnit 3.7 and JUnitPerf</li>
+	<li>The cBuffer variable used for keyGeneration has been moved locally to GenerateKey since it was a threading issue</li>
+	<li>The build file has been modified to include test running</li>
+	<li>The flushAll method is now abstract since CacheAbstractAdministrator can't know all valid scopes</li>
+	<li>Removed the retry logic for disk cache read and write (not used anymore)</li>
+	<li>Fixed an issue with the needsRefresh method which returned an invalid value when invoked first by returning true and then invoked having to return false. Both case returned true.</li>
+	<li>The doStartTag method in CacheTag has been modified to prevent returning null when cache content is missing (cache file deleted)</li>
+	<li>The doAfterBody method in CacheTag has been modified in order to prevent hitting the cache twice in some situation</li>
+	<li>The useBody method in CacheTag has been renamed to setUseBody in order to reflect its usage</li>
+	<li>LoadProperties interface added to CacheProperties</li>
+	<li>Added a NeedsRefreshException</li>
+	<li>Retrofited the changed made by Kesav Kumar in order to retrieve the sessionId correctly</li>
+	<li>Added code toughness to avoid working with invalid parameters in public methods</li>
+	<li>Magic numbers and strings are now declared as constants</li>
+	<li>Many methods are now declared as final or protected</li>
+	<li>Imports are now more accurate, no more *</li>
+	<li>Comments and some headers modified to reflect JavaDoc standard</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.1.html b/docs/wiki/OSCache 1.7.1.html
new file mode 100644
index 0000000..3323183
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.1.html	
@@ -0,0 +1,38 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(26th September 2001 - by Francois Beauregard, fbeauregard at pyxis-tech.com, and<br/>
+Alain Bergevin, abergevin at pyxis-tech.com, of Pyxis Technologies Inc.)</p>
+
+<ul>
+	<li>Cache Events</li>
+	<li>Persistence mechanism refactored</li>
+	<li>Cache Algorithms FIFO + LRU (Limit the size of the cache)</li>
+	<li>AbstractConcurrentReadCache from Doug Lea's ConcurrentReaderHashMap.<br/>
+      Should give oscache performance improvement</li>
+	<li>Disk Persistence does not need any locking strategies. Everything is handled by AbstractConcurrentReadCache</li>
+	<li>Pluggable entry refresh policies</li>
+	<li>Unlimited cache size for disk</li>
+	<li>Specify Duration using Simple Date Format or ISO-8601 as suggested by Fredrik Lindgren)<br/>
+      The next one that would make sense I think is being able to specify a specific time of day.</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.2.html b/docs/wiki/OSCache 1.7.2.html
new file mode 100644
index 0000000..f1e36d8
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.2.html	
@@ -0,0 +1,30 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(31st October 2001 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Cleaned up <em>all</em> JavaDoc messages to ensure consistency and readability (removed unnecessary CVS tags, added <code> and <pre> where needed, added @return and @param to all methods)</li>
+	<li>Renamed nbMaxEntries to cacheCapacity and cache.size property to cache.capacity (to fit better with Collections API where capacity is max size, size is current size)</li>
+	<li>Renamed algoClass to algorithmClass for clarity.</li>
+	<li>Fixed up build.xml so that test classes are compiled to a different location and not included in oscache.jar (to make it smaller footprint)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.3.html b/docs/wiki/OSCache 1.7.3.html
new file mode 100644
index 0000000..1bfe94b
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.3.html	
@@ -0,0 +1,29 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.3
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.3-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(11th November 2001 - by Francois Beauregard, fbeauregard at pyxis-tech.com)</p>
+
+<ul>
+	<li>TestCacheEntry had a test method with improper name (flush -> testFlush)</li>
+	<li>Pluggable entry refresh policy now available in the cache tag</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.4.html b/docs/wiki/OSCache 1.7.4.html
new file mode 100644
index 0000000..e89bf55
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.4.html	
@@ -0,0 +1,28 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.4
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.4-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(3rd December 2001 - by Francois Beauregard, fbeauregard at pyxis-tech.com, and<br/>
+Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Made all servlet cache components serializable (fixes bug reported on list with JRun)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 1.7.5.html b/docs/wiki/OSCache 1.7.5.html
new file mode 100644
index 0000000..3415de1
--- /dev/null
+++ b/docs/wiki/OSCache 1.7.5.html	
@@ -0,0 +1,28 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 1.7.5
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache1.7.5-ReleaseNotes"></a>Release Notes</h3>
+<p>(5th January 2002 - by Mike Cannon-Brookes, mike at atlassian.com)</p>
+
+<ul>
+	<li>Fixed up logging system slightly. All errors should now be logged with logError() and normal messages with log()</li>
+	<li>Fixed bug in build file which put oscache.properties inside the oscache.jar (resulting in it being loaded badly in some containers)</li>
+	<li>Changed cache.capacity in the default oscache.properties file to 1000. This means up to 1000 items will be cached in the default setup, and LRUCache will be used (100 seemed too small)</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.0 beta 1.html b/docs/wiki/OSCache 2.0 beta 1.html
new file mode 100644
index 0000000..12a35f8
--- /dev/null
+++ b/docs/wiki/OSCache 2.0 beta 1.html	
@@ -0,0 +1,96 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.0 beta 1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.0beta1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(19th July 2003 - by Chris Miller)</p>
+<h4><a name="OSCache2.0beta1-NewFeatures%3A"></a>New Features:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-11" title="Visit page outside Confluence">CACHE-11 </a> Cache grouping support. This allows cache entries to be placed into an arbitrary group or groups and flushed with a single flushGroup() call.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-30" title="Visit page outside Confluence">CACHE-30 </a> Added support for expiring cache entries based on a cron expression. Entries that are older than the date/time that most recently matches the cron expression will be considered stale. This is exposed to the cache tag via the 'cron' attribute. See cronTest.jsp for examples.</li>
+	<li>Event listener support has been refactored and improved. It is now possible to specify a comma-delimited list of event listeners using this property. Previously only one class could be specified. Events listed here should implement the CacheEntryEventListener and/or the ScopeEventListener interfaces.</li>
+	<li>New event CacheMapAccessEvent.STALE_HIT. This event is fired when an attempt is made to retrieve and entry from the cache, and the entry is found but is stale.</li>
+	<li>Clustering support has been added as an event listener. Currently it is implemented using <a href="http://www.javagroups.com" title="Visit page outside Confluence">JavaGroups </a>. To enable, just add the BroadcastingCacheEventListener class to the cache.event.listeners property.</li>
+	<li>Now uses Jakarta Commons Logging for all log messages. This means that the cache.debug configuration property is now ignored - use whatever logging configuration is appropriate for your logging setup instead. -Fabian Crabus</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-14" title="Visit page outside Confluence">CACHE-14, Matthias Nott </a> Now allows for content to be cached indefinitely without expiration.</li>
+	<li>The build.xml <javac ...> directives now specify debug="true".</li>
+	<li>Performance boost: When OSCache is running on JRE 1.4 or higher, LRUCache and FIFOCache use a LinkedHashSet instead of a LinkedList.</li>
+	<li>Japloy is now used to ensure source is consistently formatted.</li>
+	<li>Test cases now work on non-windows platforms. Also coverage reports added courtesy of clover.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0beta1-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes that may affect backwards compatibility:</h4>
+
+<ul>
+	<li>The cache.entryevent.classes property in the configuration file has been renamed to cache.event.listeners, since it accepts CacheEntryEventListener, ScopeEventListener and CacheMapAccessEventListener types.</li>
+	<li>The cache.persistence.classes property has been renamed to cache.persistence.class since it is only possible to specify one PersistenceListener.</li>
+	<li>For consistency, cache.unlimited_disk is now cache.unlimited.disk and cache.useHostDomainInKey is now cache.use.host.domain.in.key.</li>
+	<li>The oscache.tld file now uses a taglib 1.2 DTD.</li>
+	<li>To build OSCache, JDK 1.4.x or higher is required. There is however no runtime dependency on JDK 1.4.x.</li>
+	<li>The Cache.flushPattern() method and <cache:flush pattern="..."/> are deprecated. You are instead encouraged to group your cache entries when you add them to the cache and then use the Cache.flushGroup() method or the <cache:flush group="..."/> tag to flush an entire cache group.</li>
+	<li>Disk persistence now puts all files in the same directory. This has a number of side effects. Keys >255 chars will cause problems. Also, similar keys might get mapped to the same file. For example, it is very inadvisable to have two keys with the names 'my_key' and 'my.key'.</li>
+	<li>GeneralCacheAdministrator is no longer static. Users that relied on this behaviour can still hold onto a static reference to it with minor code changes.</li>
+	<li>When a NeedsRefreshException is thrown, it is now vital that the cache entry is either updated, or Cache.cancelUpdate(key) is called to release the lock on this cache entry. This is a consequence of the fix for <a href="http://jira.opensymphony.com/browse/CACHE-42" title="Visit page outside Confluence">CACHE-42 </a>.</li>
+	<li>CacheProperties class was removed. It didn't work on 1.7.5 anyway. The same effect can be achieved by specifying a subclass of Properties.</li>
+	<li>Autogenerated cache keys now contain the request method (eg, HEAD, GET, etc).</li>
+	<li>OSCache has been repackaged from "com.opensymphony.module.oscache.*" to "com.opensymphony.oscache.*". Any code or configuration files that refer to "com.opensymphony.module.oscache" will need to be updated.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0beta1-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-4" title="Visit page outside Confluence">CACHE-4 </a> WebSphere 3.5.x compatibility.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-5" title="Visit page outside Confluence">CACHE-5 </a> Added a mode attribute to the cache tag to allow content to be cached but not sent to the output stream. See oscacheTest.jsp for an example.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-7" title="Visit page outside Confluence">CACHE-7 </a> "cache" Tag has no "setEncoding" method.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-9" title="Visit page outside Confluence">CACHE-9 </a> It could be useful being able to specify directories relative to the web application dir. for config file and cache dir. Use new properties aware getInstance method.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-10" title="Visit page outside Confluence">CACHE-10 </a> Cannot write and use custom class implementing CacheProperties.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-13" title="Visit page outside Confluence">CACHE-13 </a> AbstractConcurrentReadCache loops indefinitely when persistRetrieve() returns null.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-14" title="Visit page outside Confluence">CACHE-14 </a> You can now specify an unlimited refresh time by supplying a negative value for the duration.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-17" title="Visit page outside Confluence">CACHE-17 </a> An example war is now included - "ant example-war". Once deployed this can be tested using "ant test-web".</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-26" title="Visit page outside Confluence">CACHE-26 </a> Security hole whereby certain keys can overwrite any file.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-28" title="Visit page outside Confluence">CACHE-28 </a> URLs can now be used as keys with disk persistence.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-31" title="Visit page outside Confluence">CACHE-31 </a> and <a href="http://jira.opensymphony.com/browse/CACHE-33" title="Visit page outside Confluence">CACHE-33 </a> The cache tag's refresh attribute will now be taken into account even if a custom refresh policy has been specified.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-34" title="Visit page outside Confluence">CACHE-34 </a> Setting properties with AbstractCacheAdministrators. New getInstance method added to ServletCacheAdministrator that takes in properties.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-35" title="Visit page outside Confluence">CACHE-35 </a> CacheFilter needs to distinguish between HEAD and GET requests.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-39" title="Visit page outside Confluence">CACHE-39 </a> and <a href="http://jira.opensymphony.com/browse/CACHE-44" title="Visit page outside Confluence">CACHE-44 </a> Synchronization with LRUCache fixed.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-42" title="Visit page outside Confluence">CACHE-42 </a> Threads will no longer race to (re)build expired or new cache entries. By default stale content will be served if available. This behaviour can be changed by setting oscache.blocking=true, which will instead cause threads to block until the new cache entry is available.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-43" title="Visit page outside Confluence">CACHE-43 </a> Taglibs have been made spec-compliant. They now follow the guidelines at <a href="http://jakarta.apache.org/taglibs/guidelines.html" title="Visit page outside Confluence">http://jakarta.apache.org/taglibs/guidelines.html</a>.</li>
+	<li>Some synchronization issues were fixed in LRUCache.getItem() and AbstractConcurrentReadCache.setMaxEntries().</li>
+	<li>ScopeEventListener classes were previously not able to be specified in the configuration even though the dispatching code was implemented. ScopeEventListeners can now be specified using the cache.event.listeners configuration property.</li>
+	<li>CacheMapAccessEvents now only fire when an attempt is made to retrieve the actual cache content for external use. Previously these events were being fired in circumstances that were not of statistical interest - for example HIT and MISS events were being fired when updating or flushing entries from the cache.</li>
+	<li>Minor bug in oscacheTestMultipleTagNoKey.jsp - some of the tag refresh times weren't correctly specified.</li>
+	<li>cachetest.jsp - the 'refresh' functionality wasn't working because the addition of the refresh parameter caused the cache key to be different. The key is now specified explicitly.</li>
+	<li>EntryRefreshPolicy is now serializable so it can be persisted to the disk cache.</li>
+	<li>ServletCacheAdministrator now sorts request parameters and filters out jsessionid so they have no impact on the generated cache key.</li>
+	<li>CacheFilter only caches successful responses (status code == SC_OK).</li>
+</ul>
+
+
+<h4><a name="OSCache2.0beta1-KnownProblems%3A%28thesehaveexistedforsometimeinthe1.x.xversionsandwillbeaddressedinanupcoming2.x.xrelease%29"></a>Known Problems: (these have existed for some time in the 1.x.x versions and will be addressed in an upcoming 2.x.x release)</h4>
+
+<ul>
+	<li>Session caches (created using the ServletCacheAdministrator) have some known limitations:<br/>
+          o Due to a workaround in the code, it is possible for a system under heavy load to get its persistent session caches confused across sessions.<br/>
+          o Session caches will not work in a clustered environment.<br/>
+          o Session caches have the same settings global settings applied to them as the application scope cache. This means that if you want a persistent cache for the application scope cache, the session caches will use it too.</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.0 beta 2.html b/docs/wiki/OSCache 2.0 beta 2.html
new file mode 100644
index 0000000..13b74e9
--- /dev/null
+++ b/docs/wiki/OSCache 2.0 beta 2.html	
@@ -0,0 +1,44 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.0 beta 2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.0beta2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(4th August 2003 - by Chris Miller)</p>
+<h4><a name="OSCache2.0beta2-NewFeatures%3A"></a>New Features:</h4>
+
+<ul>
+	<li>Now supports JavaGroups version 2.1.</li>
+	<li>JMS Clustering support has been added -Romulus Pasca.</li>
+	<li>Clustering code has been refactored. As a result of this, some of the clustering configuration has changed since beta 1 - please see the updated clustering documentation for details.</li>
+	<li>Performance enhancement: When running under JRE 1.3.x, the LRUCache will now attempt to use the Jakarta commons collections SequencedHashMap. If the commons-collections.jar is not present then the code resorts to using a LinkedList and a warning is logged. Note that under JRE 1.4.x and higher the commons-collections.jar is not required.</li>
+	<li>Config.getProperties() method added.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0beta2-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-48" title="Visit page outside Confluence">CACHE-48 </a> FastCronParser no longer requires JDK 1.4.x.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-45" title="Visit page outside Confluence">CACHE-45 </a> Fixed a serialization bug.</li>
+	<li>The CachewideEvent was not holding the event date.</li>
+	<li>Prevented an error from being logged in the CachewideEvent handling (even though no problem had occurred).</li>
+	<li>Fixed a subtle bug in the concurrent unit test.</li>
+	<li>The ServletCacheAdministrator's app scope cache is created on startup (via the CacheContextListener).</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.0.1.html b/docs/wiki/OSCache 2.0.1.html
new file mode 100644
index 0000000..6722a70
--- /dev/null
+++ b/docs/wiki/OSCache 2.0.1.html	
@@ -0,0 +1,46 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.0.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.0.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(4th November 2003 - by Chris Miller)</p>
+<h4><a name="OSCache2.0.1-Improvements%3A"></a>Improvements:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-56" title="Visit page outside Confluence">CACHE-56 </a> Refresh period is no longer mandatory.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-51" title="Visit page outside Confluence">CACHE-51 </a> Added an <cache:addgroup /> tag. This allows cache groups to be dynamically added from within a <cache:cache /> tag.</li>
+	<li>Website documentation is now bundled with the OSCache distribution.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0.1-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-59" title="Visit page outside Confluence">CACHE-59 </a> Silent mode could not be reset.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-60" title="Visit page outside Confluence">CACHE-60 </a> Fixed deadlock problem when cancelUpdate() was called while under load.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0.1-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes that may affect backwards compatibility:</h4>
+
+<ul>
+	<li>StringUtil.split() now returns a List rather than a String[].</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.0.2.html b/docs/wiki/OSCache 2.0.2.html
new file mode 100644
index 0000000..6f74681
--- /dev/null
+++ b/docs/wiki/OSCache 2.0.2.html	
@@ -0,0 +1,41 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.0.2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.0.2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(22nd January 2004 - by Mathias Bogaert)</p>
+
+<h4><a name="OSCache2.0.2-Improvements%3A"></a>Improvements:</h4>
+
+<ul>
+	<li>Website documentation updates.</li>
+	<li>Added OSCache in the Wild.</li>
+</ul>
+
+
+<h4><a name="OSCache2.0.2-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-63" title="Visit page outside Confluence">CACHE-63 </a> NullPointerException in GeneralCacheAdministrator#destroy().</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-44" title="Visit page outside Confluence">CACHE-44 </a> Multi threading issues with LRU Cache.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-66" title="Visit page outside Confluence">CACHE-66 </a> DiskPersistenceListener is not Serializable.</li>
+	<li>GeneralCacheAdministrator now creates the cache from within the constructor. This prevents possible threading issues if the cache is not initialized during application startup.</li>
+</ul>
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.0.html b/docs/wiki/OSCache 2.0.html
new file mode 100644
index 0000000..28d2df7
--- /dev/null
+++ b/docs/wiki/OSCache 2.0.html	
@@ -0,0 +1,41 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.0
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.0-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(22nd September 2003 - by Chris Miller)</p>
+<h4><a name="OSCache2.0-Improvements%3A"></a>Improvements:</h4>
+
+<ul>
+	<li>Minor FastCronParser speedup.</li>
+	<li>Made ClusterNotification constants public.</li>
+	<li>Dropped some of the logging levels from INFO down to DEBUG.</li>
+	<li>Release has been split into two - a binary release and a full release (includes source).</li>
+</ul>
+
+
+<h4><a name="OSCache2.0-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-52" title="Visit page outside Confluence">CACHE-52 </a> Fixed a problem that caused no output on Tomcat for small JSP files.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-53" title="Visit page outside Confluence">CACHE-53 </a> Updated documentation to explain that a PersistenceListener must be specified to enable caching to disk.</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-55" title="Visit page outside Confluence">CACHE-55 </a> JMS was throwing an exception on Weblogic.</li>
+	<li>Altering the cache capacity on the fly using the administrator classes wasn't working correctly.</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.1.1.html b/docs/wiki/OSCache 2.1.1.html
new file mode 100644
index 0000000..7185684
--- /dev/null
+++ b/docs/wiki/OSCache 2.1.1.html	
@@ -0,0 +1,289 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.1.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.1.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(1st May 2005 - by Andres March)</p>
+
+<h4><a name="OSCache2.1.1-Improvements%3A"></a>Improvements:</h4>
+
+<ul>
+	<li>The taglib URI was changed to <tt><a href="http://www.opensymphony.com/oscache" title="Visit page outside Confluence">http://www.opensymphony.com/oscache</a></tt> in <a href="http://jira.opensymphony.com/browse/CACHE-61" title="Visit page outside Confluence">CACHE-61</a></li>
+	<li>The DiskPersistenceListener escapes '?' now and guarantees that the filenames will be unique based on the cache key, see <a href="http://jira.opensymphony.com/browse/CACHE-110" title="Visit page outside Confluence">CACHE-110</a></li>
+	<li>Session objects in cache tags are created only if necessary, see <a href="http://jira.opensymphony.com/browse/CACHE-88" title="Visit page outside Confluence">CACHE-88</a></li>
+	<li>The disk persistence configuration key can be accessed now, see <a href="http://jira.opensymphony.com/browse/CACHE-111" title="Visit page outside Confluence">CACHE-111</a></li>
+</ul>
+
+
+
+<h4><a name="OSCache2.1.1-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li>The CacheFilter doesn't send back a 304 (not modified) response when client cache is de-activated anymore, see <a href="http://jira.opensymphony.com/browse/CACHE-116" title="Visit page outside Confluence">CACHE-116</a></li>
+	<li>CacheFilter doesn't support correctly i18N by setting encoding not properly, <a href="http://jira.opensymphony.com/browse/CACHE-38" title="Visit page outside Confluence">CACHE-38</a> and <a href="http://jira.opensymphony.com/browse/CACHE-159" title="Visit page outside Confluence">CACHE-159</a></li>
+	<li>Cron expressions - leap days not always matched correctly, <a href="http://jira.opensymphony.com/browse/CACHE-157" title="Visit page outside Confluence">CACHE-157</a></li>
+	<li>FindBugs doesn't report that the usage of GetResource may be unsafe if class Config is extended anymore, see <a href="http://jira.opensymphony.com/browse/CACHE-108" title="Visit page outside Confluence">CACHE-108</a></li>
+	<li>ConcurrentModificationException on flushGroup, see <a href="http://jira.opensymphony.com/browse/CACHE-127" title="Visit page outside Confluence">CACHE-127</a></li>
+	<li>Exception not thrown when not serializable object is persisted instead stack trace is persisted, see <a href="http://jira.opensymphony.com/browse/CACHE-112" title="Visit page outside Confluence">CACHE-112</a></li>
+	<li>A few concurrency issues were fixed, see <a href="http://jira.opensymphony.com/browse/CACHE-170" title="Visit page outside Confluence">CACHE-170</a>, <a href="http://jira.opensymphony.com/browse/CACHE-167" title="Visit page outside Confluence">CACHE-167</a>, <a href="http://jira.opensymphony.com/browse/CACHE-127" title="Visit page outside Confluence">CACHE-127</a></li>
+</ul>
+
+
+<h4><a name="OSCache2.1.1-Changesthatmayaffectbackwardscompatibility%3A"></a>Changes that may affect backwards compatibility:</h4>
+
+<ul>
+	<li>The improvement <a href="http://jira.opensymphony.com/browse/CACHE-88" title="Visit page outside Confluence">CACHE-88</a> may change the behaviour of the application, because a session object isn't created anymore even if it wasn't necessary. A web application may react different to a not existing session object.</li>
+	<li>The URI change of <a href="http://jira.opensymphony.com/browse/CACHE-61" title="Visit page outside Confluence">CACHE-61</a> from <tt>/oscache</tt> to <tt><a href="http://www.opensymphony.com/oscache" title="Visit page outside Confluence">http://www.opensymphony.com/oscache</a></tt> affects all JSP's which explicit use the old URI.</li>
+</ul>
+
+
+<h4><a name="OSCache2.1.1-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(15 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170">CACHE-170</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170">Data race handling Cache.updateStates results in Thread hangs when the blocking mode is used in concurrence</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-167"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-167">CACHE-167</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-167">removeEntry not synchronized</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-159"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-159">CACHE-159</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-159">CacheFilter does not set encoding properly</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-157"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-157">CACHE-157</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-157">Cron expressions - leap days not always matched correctly</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-131"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-131">CACHE-131</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-131">JavaDoc: Missing class description - CacheContextListener</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-127"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-127">CACHE-127</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-127">ConcurrentModificationException on flushGroup</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-116"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-116">CACHE-116</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-116">CacheFilter sends back a 304 (not modified) response when client cache is de-activated</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-112"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-112">CACHE-112</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-112">Exception not thrown when not serializable object is persisted instead stack trace is persisted!</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-111"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-111">CACHE-111</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-111">public access for disk persistence configuration key</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-110"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-110">CACHE-110</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-110">DiskPersistenceListener should escape '?'</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-109"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-109">CACHE-109</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-109">cache.blocking parameter missing in oscache.properties</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-108"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-108">CACHE-108</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-108">FindBugs reports: Usage of GetResource may be unsafe if class Config is extended</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-88"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-88">CACHE-88</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-88">Don't create session object in cache tags unless necessary</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-61"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-61">CACHE-61</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-61">Taglib URI Attribute</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-38"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-38">CACHE-38</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-38">oscache filter doesn't support correctly i18N</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21441&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.1.html b/docs/wiki/OSCache 2.1.html
new file mode 100644
index 0000000..b2c19d0
--- /dev/null
+++ b/docs/wiki/OSCache 2.1.html	
@@ -0,0 +1,312 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(18th January 2005 - by Andres March)</p>
+<h4><a name="OSCache2.1-NewFeatures%3A"></a>New Features:</h4>
+<ul>
+	<li>Added HashDiskPersistenceListner <a href="http://jira.opensymphony.com/browse/CACHE-132" title="Visit page outside Confluence">CACHE-132 </a> that hashes file names in order to eliminate nasty characters and overly long names</li>
+	<li>Added property that allows cache entries to only be persisted when the memory capacity has been exceeded. The property is called: cache.persistence.overflow.only. It defaults to false for backwards compatibility meaning all cache entries are persisted when a listener has been registered. See <a href="http://jira.opensymphony.com/browse/CACHE-133" title="Visit page outside Confluence">CACHE-133 </a></li>
+	<li>Check If-Modified-Since header in cache filter to increase performance, see <a href="http://jira.opensymphony.com/browse/CACHE-58" title="Visit page outside Confluence">CACHE-58 </a> and <a href="http://jira.opensymphony.com/browse/CACHE-70" title="Visit page outside Confluence">CACHE-70 </a></li>
+</ul>
+
+
+<h4><a name="OSCache2.1-Improvements%3A"></a>Improvements:</h4>
+
+<ul>
+	<li>Updated jgroups jar regarding changed package name <a href="http://jira.opensymphony.com/browse/CACHE-85" title="Visit page outside Confluence">CACHE-85 </a>, <a href="http://jira.opensymphony.com/browse/CACHE-126" title="Visit page outside Confluence">CACHE-126</a> and configuration based upon recommendations from Bela Ban (javagroups maintainer).</li>
+	<li>More evenly distributed disk caching, see <a href="http://jira.opensymphony.com/browse/CACHE-94" title="Visit page outside Confluence">CACHE-94 </a></li>
+	<li>Public access for configuration properties, see <a href="http://jira.opensymphony.com/browse/CACHE-92" title="Visit page outside Confluence">CACHE-92 </a></li>
+	<li>Public method to clear cache, see <a href="http://jira.opensymphony.com/browse/CACHE-104" title="Visit page outside Confluence">CACHE-104 </a>, <a href="http://jira.opensymphony.com/browse/CACHE-68" title="Visit page outside Confluence">CACHE-68</a></li>
+	<li>Output the scope name's in toString() of ScopeEventListenerImpl, see <a href="http://jira.opensymphony.com/browse/CACHE-95" title="Visit page outside Confluence">CACHE-95 </a></li>
+	<li>Call get() method on put() method call, see <a href="http://jira.opensymphony.com/browse/CACHE-105" title="Visit page outside Confluence">CACHE-105 </a></li>
+	<li>Library updates
+	<ul>
+		<li>upgrade to Commons Logging 1.0.4, see <a href="http://jira.opensymphony.com/browse/CACHE-102" title="Visit page outside Confluence">CACHE-102 </a></li>
+		<li>upgrade to Commons Collections 3.1, see <a href="http://jira.opensymphony.com/browse/CACHE-103" title="Visit page outside Confluence">CACHE-103 </a></li>
+	</ul>
+	</li>
+	<li>Moved all docs to <a href="http://wiki.opensymphony.com/display/CACHE/Home" title="Visit page outside Confluence">wiki</a></li>
+	<li>Website documentation updates.</li>
+</ul>
+
+
+<h4><a name="OSCache2.1-BugFixes%3A"></a>Bug Fixes:</h4>
+
+<ul>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-73" title="Visit page outside Confluence">CACHE-73 </a> - NullpointerException after deserialization of AbstractConcurrentReadCache</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-98" title="Visit page outside Confluence">CACHE-98 </a> - Disk cache not getting served first time for long keys</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-107" title="Visit page outside Confluence">CACHE-107 </a> - flushEntry does not behave correctly in cluster</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-118" title="Visit page outside Confluence">CACHE-118</a> - Updating groups doesn't work</li>
+	<li><a href="http://jira.opensymphony.com/browse/CACHE-119" title="Visit page outside Confluence">CACHE-119</a> - flush does not work correctly in a clustered environment</li>
+</ul>
+
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="3" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(21 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-133"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-133">CACHE-133</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-133">added cache.persistence.overflow.only property</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-132"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-132">CACHE-132</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-132">Added HashDiskPersistenceListner</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-126"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-126">CACHE-126</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-126">java.lang.NoClassDefFoundError: org/javagroups/blocks/NotificationBus$Consumer</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-119"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-119">CACHE-119</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-119">flush does not work correctly in a clustered environment</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-118"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-118">CACHE-118</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-118">Updating groups doesn't work</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-107"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-107">CACHE-107</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-107">flushEntry does not behave correctly in cluster</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-105"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-105">CACHE-105</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-105">call get() method on put() method call</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-104"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-104">CACHE-104</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-104">Destroy cache</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-103"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-103">CACHE-103</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-103">upgrade to Commons Collections 3.1</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-102"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-102">CACHE-102</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-102">upgrade to Commons Logging 1.0.4 </a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-98"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-98">CACHE-98</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-98">Disk cache not getting served first time for long keys</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-95"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-95">CACHE-95</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-95">Output the scope name's in toString()</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-94"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-94">CACHE-94</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-94">More evenly distributed disk caching</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-92"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-92">CACHE-92</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-92">public access for configuration properties</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-89"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-89">CACHE-89</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-89">java.lang.NullPointerException : AbstractCacheAdministrator.finalizeListeners</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-85"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-85">CACHE-85</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-85">upgrade to JavaGroups 2.2.7</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-73"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-73">CACHE-73</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-73">NullpointerException after deserialization of AbstractConcurrentReadCache</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-72"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-72">CACHE-72</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-72">NullPointerException in AbstractConcurrentReadCache.clear</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-71"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-71">CACHE-71</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-71">Flush and refresh of cached pages fail under heavy load</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-70"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-70">CACHE-70</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-70">last modified problem</a>
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-58"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21223&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-58">CACHE-58</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-58"> Check If-Modified-Since header in cache filter</a>
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.2 RC.html b/docs/wiki/OSCache 2.2 RC.html
new file mode 100644
index 0000000..91ef3af
--- /dev/null
+++ b/docs/wiki/OSCache 2.2 RC.html	
@@ -0,0 +1,368 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.2 RC
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.2RC-ReleaseNotesReleaseCandidate"></a>Release Notes - Release Candidate</h3>
+
+<p>(18th September 2005 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.2RC-Besidesbugsbeingfixed%2CmajorimprovementshavebeenmadetotheCacheFilterinmanyways%3A"></a>Besides bugs being fixed, major improvements have been made to the CacheFilter in many ways:</h4>
+
+<ul>
+	<li>Default initialization of the last modified header which reduces transaction overhead and server load</li>
+	<li>Support of GZip filters in the filter chain</li>
+	<li>Custom key generation by subclassing CacheFilter or by implementing a special interface</li>
+	<li>Preserving more http headers, e.g. the expires header</li>
+	<li>Special handling for fragments of a page</li>
+	<li>Avoids session creation for application scope pages</li>
+	<li>Multiple matching cache filters won't dead-lock the response anymore</li>
+</ul>
+
+
+<h4><a name="OSCache2.2RC-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(22 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-189"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-189">CACHE-189</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-189">AbstractDiskPersistenceListener.store hangs on exception</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-185"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-185">CACHE-185</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-185">Filtered requests will be re-requested twice</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-184"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-184">CACHE-184</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-184">Filter deadlock with external apps (mostly spiders)</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-179"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-179">CACHE-179</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-179">Provider interface for method createCacheKey</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-174"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-174">CACHE-174</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-174">Regression in fix of CACHE-170: UpdateStateEntry may leak when entry are removed</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-173"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-173">CACHE-173</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-173">NullPointerException while flushing inexistant group</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170">CACHE-170</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-170">Data race handling Cache.updateStates results in Thread hangs when the blocking mode is used in concurrence</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-169"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-169">CACHE-169</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-169">Default initialization of the last modified header</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-161"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/issue_subtask.gif" alt="Sub-task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-161">CACHE-161</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-161">CacheFilter easier sub-classing via isCacheable</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-160"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-160">CACHE-160</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-160">ExpiresRefreshPolicy always set in CacheFilter</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-155"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-155">CACHE-155</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-155">Support of GZip filters in the filter chain</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-154"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-154">CACHE-154</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-154">NullPointerException in JavaGroupsBroadcastingListener</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-148"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-148">CACHE-148</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-148">getInstance call not thread-safe</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-144"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-144">CACHE-144</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-144">CacheTag doesn't  clear variables in doStartTag / doFinally</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-143"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-143">CACHE-143</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-143">Report expected expiry to clients/browsers/proxy</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-138"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-138">CACHE-138</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-138">Document new parameters in the wiki</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-135"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-135">CACHE-135</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-135">CacheFilter for fragements of a page</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-129"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-129">CACHE-129</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-129">CacheFilter will create useless sessions for application-scope pages</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-128"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-128">CACHE-128</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-128">Multiple matching filters will dead-lock the response</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-120"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-120">CACHE-120</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-120">New nocache option when body contains a jsessionid</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-83"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-83">CACHE-83</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-83">CacheHttpServletResponseWrapper & ResponseContent dont preserver Http headers</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-69"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-69">CACHE-69</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-69">Custom Key Generation on CacheFilter</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21450&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.2.html b/docs/wiki/OSCache 2.2.html
new file mode 100644
index 0000000..df0ec4f
--- /dev/null
+++ b/docs/wiki/OSCache 2.2.html	
@@ -0,0 +1,226 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.2-ReleaseNotesFinal"></a>Release Notes - Final</h3>
+
+<p>(6th November 2005 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.2-Additionallytothe2.2RCOSCache2.2RCimprovements%2Cthefinalreleasewasenhancedby%3A"></a>Additionally to the <a href="OSCache 2.2 RC.html" title="OSCache 2.2 RC">2.2 RC</a> improvements, the final release was enhanced by:</h4>
+
+<ul>
+	<li>Allow cache group definition in CacheFilter</li>
+	<li>Option to specify when to send Expires-Header</li>
+	<li>Allow disabling initial set of the last modified header</li>
+	<li>Continuous Integration and Dependency Management with Ivy</li>
+	<li>Update to JGroups 2.2.8</li>
+</ul>
+
+
+<h4><a name="OSCache2.2-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(12 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-223"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-223">CACHE-223</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-223">completeUpdate never being called after startUpdate() has been called, OSCache hangs for that key</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-211"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-211">CACHE-211</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-211">Create check sums for the distribution files</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-210"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-210">CACHE-210</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-210">Review: If last test-base and last test-web overwrite previous unit test reports</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-204"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-204">CACHE-204</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-204">Allow disabling initial set of the last modified header</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-203"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-203">CACHE-203</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-203">Change JSP tag URI in pages of example war</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-202"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-202">CACHE-202</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-202">Expires header should not be inital set in fragments</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-201"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-201">CACHE-201</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-201">Defined interface for ICacheKeyProvider not used in CacheFilter</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-199"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-199">CACHE-199</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-199">Continuous Integration and Dependency Management with Ivy</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-198"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-198">CACHE-198</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-198">Update to JGroups 2.2.8</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-196"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-196">CACHE-196</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-196">Option to specify when to send Expires-Header</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-195"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-195">CACHE-195</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-195">Allow cache group generation in CacheFilter</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-194"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-194">CACHE-194</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-194">Update Documentation</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21550&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.3.1.html b/docs/wiki/OSCache 2.3.1.html
new file mode 100644
index 0000000..3e6e9ec
--- /dev/null
+++ b/docs/wiki/OSCache 2.3.1.html	
@@ -0,0 +1,107 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.3.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.3.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(19th June 2006 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.3.1-Thismaintenancereleaseof2.3hasoneenhancement%3A"></a>This maintenance release of 2.3 has one enhancement:</h4>
+
+<ul>
+	<li>CacheFilter: Default initialization of the Cache-Control max-age</li>
+</ul>
+
+
+<h4><a name="OSCache2.3.1-Bugfixes%3A"></a>Bug fixes:</h4>
+
+<ul>
+	<li>Cache.flushAll(Date flushDate) won't throw NeedsRefreshException when flush date is not yet reached anymore</li>
+	<li>No NoSuchElementException at Cache.putInCache() anymore</li>
+</ul>
+
+
+<h4><a name="OSCache2.3.1-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(3 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-246"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-246">CACHE-246</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-246">java.util.NoSuchElementException during at com.opensymphony.oscache.base.Cache.putInCache()</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-241"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-241">CACHE-241</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-241">Cache.flushAll(Date flushDate) throws NeedsRefreshException when flush date is not yet reached</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-240"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-240">CACHE-240</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-240">Default initialization of the Cache-Control max-age</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21630&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.3.2.html b/docs/wiki/OSCache 2.3.2.html
new file mode 100644
index 0000000..5bcf665
--- /dev/null
+++ b/docs/wiki/OSCache 2.3.2.html	
@@ -0,0 +1,104 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.3.2
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.3.2-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(23rd July 2006 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.3.2-Thismaintenancereleaseof2.3.1hasoneenhancement%3A"></a>This maintenance release of 2.3.1 has one enhancement:</h4>
+
+<ul>
+	<li>The removeEntry method in the Cache removes the entry from its groups now</li>
+</ul>
+
+
+<h4><a name="OSCache2.3.2-Bugfixes%3A"></a>Bug fixes:</h4>
+
+<ul>
+	<li>Method addGroupMappings leads to inconsistent memory cache if a persistent cache group exists</li>
+	<li>Cache group is updated if entry is removed (duplicate)</li>
+</ul>
+
+
+<h4><a name="OSCache2.3.2-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(3 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-244"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-244">CACHE-244</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-244">Cache group is not updated if entry is removed</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-188"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-188">CACHE-188</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-188">removeEntry should update group mappings</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-181"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-181">CACHE-181</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-181">addGroupMappings leads to inconsistent Memory-Cache</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21651&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.3.html b/docs/wiki/OSCache 2.3.html
new file mode 100644
index 0000000..ca5a433
--- /dev/null
+++ b/docs/wiki/OSCache 2.3.html	
@@ -0,0 +1,270 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.3
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.3-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(6th March 2006 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.3-ThisreleaseincludesadditionalimprovementstotheCacheFilter%3A"></a>This release includes additional improvements to the CacheFilter:</h4>
+
+<ul>
+	<li>CRON expressions to expire content at specific dates and/or times</li>
+	<li>Pluggable EntryRefreshPolicy</li>
+	<li>Reduced memory consumption</li>
+</ul>
+
+
+<h4><a name="OSCache2.3-Diskpersistence%3A"></a>Disk persistence:</h4>
+
+<ul>
+	<li>Faster disk persistence</li>
+	<li>Avoid DiskPersistenceListener deadlocks if process has no rights to delete cache file</li>
+</ul>
+
+
+<h4><a name="OSCache2.3-Furtherchangesare%3A"></a>Further changes are:</h4>
+
+<ul>
+	<li>new JSP tag <em>addgroups</em></li>
+	<li>interface to get a list of the cache event listeners</li>
+	<li>commons collection dependency removed</li>
+	<li>Java 1.3 support dropped</li>
+</ul>
+
+
+<h4><a name="OSCache2.3-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(14 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-235"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-235">CACHE-235</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-235">Pluggable EntryRefreshPolicy for CacheFilter</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-230"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-230">CACHE-230</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-230">CacheFilter Tutorial</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-229"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-229">CACHE-229</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-229">Tomcat 5.5.12 throws IllegalStateException on getId() / fixed Servlet Spec 2.4</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-228"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-228">CACHE-228</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-228">Add CRON expressions to CacheFilter to expire content at specific dates and/or times.</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-227"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-227">CACHE-227</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-227">Remove commons collections from distribution</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-226"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-226">CACHE-226</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-226">Drop Java 1.3 support</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-222"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-222">CACHE-222</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-222">Add new JSP tag addGroups</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-217"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-217">CACHE-217</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-217">Avoid DiskPersistenceListener deadlocks if process has no rights to delete cache file</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-216"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-216">CACHE-216</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-216">Review CacheFilter against Servlet 2.4 spec.</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-214"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-214">CACHE-214</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-214">Reduce memory consumption of ResponseContent</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-200"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-200">CACHE-200</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-200">add 'getCacheEventListenerList()' to Cache class</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-197"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-197">CACHE-197</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-197">Speed up disk persistence</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-183"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-183">CACHE-183</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-183">HashDiskPersistenceListener / MessageDigest not thread safe</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-136"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-136">CACHE-136</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-136">SequencedHashMap is deprecated in commons collections 3.1</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21570&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.4.1.html b/docs/wiki/OSCache 2.4.1.html
new file mode 100644
index 0000000..19a1567
--- /dev/null
+++ b/docs/wiki/OSCache 2.4.1.html	
@@ -0,0 +1,97 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache 2.4.1
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="OSCache2.4.1-ReleaseNotes"></a>Release Notes</h3>
+
+<p>(7th July 2007 - by Lars Torunski)</p>
+
+<h4><a name="OSCache2.4.1-Thismaintenancereleaseof2.4.1hastwobugfixes%3A"></a>This maintenance release of 2.4.1 has two bug fixes:</h4>
+
+<ul>
+	<li>The cacheFlushed method is not being invoked on the CacheEntryEventListener</li>
+	<li>CacheFilter max-age parameter MAX_AGE_NO_INIT not set properly</li>
+</ul>
+
+
+<h4><a name="OSCache2.4.1-JIRAIssueList"></a>JIRA Issue List</h4>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(3 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-297"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-297">CACHE-297</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-297">max-age parameter not set on ResponseContent object returned from cache when using MAX_AGE_NO_INIT</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-296"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-296">CACHE-296</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-296">For a Cache class the cacheFlushed method is not being invoked on the CacheEntryEventListener.</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-279"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-279">CACHE-279</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-279">LRUCache loses entries when updated by mutliple threads.</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21705&sorter/field=issuekey&sorter/order=DESC/images/icons/status_closed.gif" alt="Closed" border="0" />Closed
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/OSCache 2.4.html b/docs/wiki/OSCache 2.4.html
new file mode 100644
index 0000000..d2d6add
--- /dev/null
+++ b/docs/wiki/OSCache 2.4.html	
@@ -0,0 +1,496 @@
+<html>
+<head>
+<title>OSCache - OSCache 2.4</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<h3><a name="OSCache2.4-ReleaseNotes"></a>Release Notes</h3>
+
+		<p>(1st Mai 2007 - by Lars Torunski)</p>
+
+		<h4><a name="OSCache2.4-Newfeaturesandenhancements"></a>New
+		features and enhancements</h4>
+
+		<p>Furthermore the next major release 2.4 enhances the CacheFilter
+		and allows a better integration with the <a
+			href="http://www.springframework.org/"
+			title="Visit page outside Confluence">Spring Framework</a> and <a
+			href="JMX Monitoring.html" title="JMX Monitoring">JMX Monitoring</a>.</p>
+
+		<ul>
+			<li>Setting CacheFilter parameters runtime</li>
+			<li>Lazy initialization in CacheFilter in order to ease spring
+			integration</li>
+			<li>Allow disabling cacheing for special http methods (e.g.
+			POST/DELETE/PUT) in CacheFilter</li>
+			<li>CacheFilter allow reentrance over different filter
+			configurations</li>
+		</ul>
+
+
+		<ul>
+			<li>Hibernate 3.2 integration support</li>
+			<li>JMX Monitoring/Administration via Spring</li>
+			<li>Improve oscache.properties loading</li>
+			<li>Performance improvment for large disk persistence usage</li>
+		</ul>
+
+
+		<h4><a name="OSCache2.4-UpgradeGuide"></a>Upgrade Guide</h4>
+
+		<ul>
+			<li>Due to the enhancements in the CacheFilter and method
+			signature changes, it's recommended to recompile your code.</li>
+			<li>Due to changes for CACHE-284 the handling of the listeners
+			have been changed: Before OSCache 2.4 objects which implemented
+			different CacheEventListener (e.g. CacheEntryEventListener and
+			CacheMapAccessEventListener) had to be added twice, because the
+			listeners where registrated only for one special event listener. With
+			OSCache 2.4 only the object has to be added to the list without the
+			2nd parameter 'type of the listener'.</li>
+		</ul>
+
+
+		<h4><a name="OSCache2.4-JIRAIssueList"></a>JIRA Issue List</h4>
+
+		<p><a name="jiraissues">
+		<table width="100%" cellspacing="0" class="grid">
+			<tr>
+				<th colspan="4" style="text-align: left;"><a
+					href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img
+					src="/images/icons/refresh_16.png" height="16" width="16"
+					border="0" align="right" title="refresh" /></a> <a
+					href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC&tempMax=30&reset=true&">OpenSymphony
+				JIRA</a> <span class="smalltext">(29 issues)</span></th>
+			</tr>
+			<tr>
+				<th style="text-align: left;">T</th>
+				<th style="text-align: left;">Key</th>
+				<th style="text-align: left;">Summary</th>
+				<th style="text-align: left;">Status</th>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-260"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-260">CACHE-260</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-260">NullPointerException
+				in AbstractConcurrentReadCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-295"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-295">CACHE-295</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-295">Hibernate
+				3.2 integration support</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-215"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-215">CACHE-215</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-215">Setting
+				CacheFilter parameters runtime</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-99"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-99">CACHE-99</a></td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-99">Use
+				lazy initialization in cache filter in order to ease spring
+				integration</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-258"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-258">CACHE-258</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-258">NullPointerException
+				when using putCache(key, val) in LRUCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-273"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-273">CACHE-273</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-273">Update
+				to Commons Logging 1.1</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-253"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-253">CACHE-253</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-253">Migrate
+				from CVS to SVN</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-261"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-261">CACHE-261</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-261">Check
+				javadoc of Cache.cancelUpdate on key not being updated </a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-163"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/issue_subtask.gif"
+					alt="Sub-task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-163">CACHE-163</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-163">CacheFilter
+				easier sub-classing via pre- and post-processes</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-162"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/issue_subtask.gif"
+					alt="Sub-task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-162">CACHE-162</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-162">CacheFilter
+				easier sub-classing via useCache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-272"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-272">CACHE-272</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-272">Allow
+				disabling of cacheing special http methods (e.g. POST/DELETE/PUT) in
+				CacheFilter</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-277"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-277">CACHE-277</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-277">CacheFilter
+				should allow reentrance over different filter configurations</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-283"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-283">CACHE-283</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-283">Improve
+				oscache.properties loading</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-266"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-266">CACHE-266</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-266">ServletCacheAdministrator
+				no longer a "Servlet Singleton"</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-267"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-267">CACHE-267</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-267">SplitServletOutputStream
+				doesn't pass flush() on to underlying stream</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-141"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-141">CACHE-141</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-141">CacheFilter
+				easier sub-classing</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-288"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-288">CACHE-288</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-288">Error
+				in the HashDiskPersistenceListener byteArrayToHexString</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-264"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-264">CACHE-264</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-264">problem
+				with not escaped group names and their filenames for disk
+				persistence</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-255"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-255">CACHE-255</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-255">
+				AbstractConcurrentReadCache#put(Object key, Object value) may return
+				a wrong value</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-249"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-249">CACHE-249</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-249">Performance
+				improvment for large disk persistence usage</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-293"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-293">CACHE-293</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-293">Allow
+				to specify a different oscache.properties file for Hibernate</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-278"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-278">CACHE-278</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-278">Filter
+				ignores max-age parameter when serving from cache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-284"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-284">CACHE-284</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-284">Cache.dispatchCacheEntryEvent
+				and Cache.addEventListener implementations are inconsistent</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-274"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-274">CACHE-274</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-274">new
+				method getIntialContext JMSBroadcastingListener</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-263"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/task.gif"
+					alt="Task" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-263">CACHE-263</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-263">Run
+				FindBugs 1.1.3 against current source code</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-292"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/bug.gif"
+					alt="Bug" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-292">CACHE-292</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-292">CacheFilter
+				max-age default and error-case initialisation are wrong</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-290"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-290">CACHE-290</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-290">Bad
+				Practice</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowAlternate">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-178"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/newfeature.gif"
+					alt="New Feature" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-178">CACHE-178</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-178">JMX
+				Monitoring/Administration via Spring</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+			<tr class="rowNormal">
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-252"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/improvement.gif"
+					alt="Improvement" border="0" /></a></td>
+				<td nowrap="true"><a
+					href="http://jira.opensymphony.com/browse/CACHE-252">CACHE-252</a>
+				</td>
+				<td><a href="http://jira.opensymphony.com/browse/CACHE-252">Log
+				warning if user tries to set max entries on an unlimited cache</a></td>
+				<td nowrap="true"><img
+					src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21660&sorter/field=priority&sorter/order=DESC/images/icons/status_closed.gif"
+					alt="Closed" border="0" />Closed</td>
+			</tr>
+		</table></p>
+
+
+
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/OSCache in the Wild.html b/docs/wiki/OSCache in the Wild.html
new file mode 100644
index 0000000..0e734a4
--- /dev/null
+++ b/docs/wiki/OSCache in the Wild.html	
@@ -0,0 +1,37 @@
+<html>
+    <head>
+        <title>OSCache - 
+        OSCache in the Wild
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>The following are some of the sites that are using OSCache in production. This is far from an exhaustive list of course! If you have or know of a site using OSCache, please let us know so we can add it to the list. While not required, any performance figures, load levels or case studies that you can include would be greatly appreciated.</p>
+
+<ul>
+	<li><a href="http://www.theserverside.com/" title="Visit page outside Confluence">The Server Side</a></li>
+	<li><a href="http://www.javablogs.com/" title="Visit page outside Confluence">JavaBlogs</a></li>
+	<li><a href="http://www.jroller.com/" title="Visit page outside Confluence">JRoller</a></li>
+	<li><a href="http://www.javalobby.org/" title="Visit page outside Confluence">JavaLobby</a></li>
+	<li><a href="http://www.premierleague.com/" title="Visit page outside Confluence">Premier League</a> - Some details are available <a href="http://www.objectivity.co.uk/Customers/Case+studies/FAPL.htm" title="Visit page outside Confluence">here</a>.</li>
+	<li><a href="http://www.blueyonder.co.uk/" title="Visit page outside Confluence">Blue Yonder</a> - Handles 10 million hits per month.</li>
+	<li><a href="http://www.officiallondontheatre.co.uk/" title="Visit page outside Confluence">Society of London Theatre</a> - Handles 7 million hits (900,000 page views) per month.</li>
+	<li><a href="http://www.burlington.co.uk/" title="Visit page outside Confluence">Burlington Paintings</a></li>
+	<li><a href="http://www.bennetts.com/" title="Visit page outside Confluence">Bennetts</a></li>
+	<li><a href="http://www.swebtec.com/" title="Visit page outside Confluence">Swebtec</a></li>
+	<li><a href="http://eq2players.station.sony.com/" title="Visit page outside Confluence">Sony Online Entertainment - Everquest2 Players' Site</a> - Handles over 1 million page views per day.</li>
+	<li><a href="http://www.searchmorph.com/" title="Visit page outside Confluence">SearchMorph</a> - Specialized search engine for javadoc-generated pages, and an experimental Wikipedia search engine, both using Lucene. See also my informal benchmark writeups, <a href="http://www.searchmorph.com/wp/2005/01/17/speed-of-jsp-caching-with-oscache/" title="Visit page outside Confluence">"speed-of-jsp-caching-with-oscache"</a> and <a href="http://www.searchmorph.com/wp/2005/01/14/oscache-rocks/" [...]
+	<li><a href="http://www.stockmorph.com/" title="Visit page outside Confluence">StockMorph</a> - JSP pages are hidden behind mod_rewrite magic, however oscache is used in the JSP, and CacheFilter for the servlets</li>
+</ul>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Requirements.html b/docs/wiki/Requirements.html
new file mode 100644
index 0000000..57b0e79
--- /dev/null
+++ b/docs/wiki/Requirements.html
@@ -0,0 +1,41 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Requirements
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p>OSCache can be used directly to provide caching for any Java application. Using the <a href="JSP Tags.html" title="JSP Tags">OSCache tag library</a> requires <b>Servlet 2.3</b> and <b>JSP 1.2</b> support (included in J2EE 1.3) to run properly. There is no dependency on a servlet container if the <a href="API Usage.html" title="API Usage">OSCache API</a> is used directly.</p>
+
+<p>So far OSCache has been tested in the following application servers and web containers:</p>
+
+<ul>
+	<li><a href="http://www.orionserver.com/" title="Visit page outside Confluence">OrionServer</a>(version 1.4.0 and above)</li>
+	<li><a href="http://www.macromedia.com/" title="Visit page outside Confluence">Macromedia JRun</a> (version 3.0 and above)</li>
+	<li><a href="http://e-docs.bea.com/" title="Visit page outside Confluence">BEA WebLogic Server</a> (should work on version 8.1 and above)</li>
+	<li><a href="http://www.ibm.com/websphere" title="Visit page outside Confluence">IBM Websphere</a> (tested on version 5.0)</li>
+	<li>Silverstream (tested on version 3.7.4)</li>
+	<li><a href="http://www.caucho.com/" title="Visit page outside Confluence">Caucho Resin</a> (version 1.2.3 and above)</li>
+	<li><a href="http://jakarta.apache.org/tomcat" title="Visit page outside Confluence">Tomcat</a> (version 4.0 and above)</li>
+	<li><a href="http://www.iplanet.com/" title="Visit page outside Confluence">iPlanet</a> (tested on version 6.0. Note that the caching filter has not been tested, only the taglibs)</li>
+</ul>
+
+
+<p>This does not mean it will not run on other servers! It should run on any specification compliant container. If you have run OSCache successfully in other servers, please let us know and we'll add to this list.</p>
+
+<p>The <a href="CacheFilter.html" title="CacheFilter">Caching Filter</a> (for caching entire pages, and binary content such as GIFs and PDFs) requires Servlet 2.3 support. It is known to work on Orion, BEA WebLogic Server and Tomcat 4.0.</p>
+
+<p>OSCache requires at least <b>Java 1.4</b> for the <a href="Installation Guide.html" title="Installation Guide">installation</a>.</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Roadmap.html b/docs/wiki/Roadmap.html
new file mode 100644
index 0000000..7fabcb8
--- /dev/null
+++ b/docs/wiki/Roadmap.html
@@ -0,0 +1,372 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Roadmap
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="Roadmap-Scope"></a>Scope</h3>
+
+<p>This page and the <a href="https://oscache.dev.java.net/servlets/ProjectMailingListList" title="Visit page outside Confluence">mailing list</a> are provided for discussion purposes about the roadmap of OSCache and discussing new features and improvements. See also the <a href="http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:roadmap-panel" title="Visit page outside Confluence">JIRA - Road Map</a> for more details or vote for issues in <a href=" [...]
+
+<h3><a name="Roadmap-OSCache3.0"></a>OSCache 3.0</h3>
+
+<p>The primary goal of this release is to make OSCache more reliable and easier to use and maintain.  </p>
+<ul>
+	<li>Reliability and maintainability will be achieved by replacing the core cache storage classes with simpler ones that sync on coarse-grained cache operations.  The memory cache, for instance, will use a hash map for its internal storage.  Existing features will be provided at a higher level, so to avoid conflicting with the storage management logic.</li>
+	<li>Usability will be improved through a refactored API that extends the java.util.Map interface.  Some of the the things the new API will change:
+	<ul>
+		<li>Remove NeedsRefreshException (if you have to ask, then you shouldn't care).</li>
+		<li>Support for Object keys instead of only strings.</li>
+		<li>Support for cache regions to enable easier management of multiple caches.</li>
+		<li>Configuration via Spring (yes, it's a buzzword).</li>
+		<li>Administration and statistics.</li>
+	</ul>
+	</li>
+</ul>
+
+
+<p>Furthermore we discuss a <a href="Chain Caching Model.html" title="Chain Caching Model">Chain Caching Model</a> internal.</p>
+
+<p><a name="jiraissues">
+    <table  width="100%" cellspacing="0" class="grid">
+        <tr>
+            <th colspan="4" style="text-align: left;">
+                                    <a href="/spaces/doexportspace.action?key=CACHE&macro.refresh=true#jiraissues"><img src="/images/icons/refresh_16.png" height="16" width="16" border="0" align="right" title="refresh"/></a>
+                                <a href="http://jira.opensymphony.com/secure/IssueNavigator.jspa?&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC&tempMax=25&reset=true&">OpenSymphony JIRA</a>
+                <span class="smalltext">(22 issues)</span>
+            </th>
+        </tr>
+        <tr>
+                        <th style="text-align: left;">
+                T                                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                Key                                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                Summary                                                                                                                                            </th>
+                        <th style="text-align: left;">
+                                                                                                                Status                                                                            </th>
+                    </tr>
+                                            <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-289"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-289">CACHE-289</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-289">Change AbstractConcurrentReadCache to use backport-util-concurrent classes</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-225"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/task.gif" alt="Task" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-225">CACHE-225</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-225">Update to JGroups 2.2.9.1</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-220"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-220">CACHE-220</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-220">Using a weak referenced cache for overflow capability</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-172"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-172">CACHE-172</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-172">Easier API usage for developers</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-158"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-158">CACHE-158</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-158">cache.capacity and GeneralCacheAdministrator.setCacheCapacity problem</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_inprogress.gif" alt="In Progress" border="0" />In Progress
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-152"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-152">CACHE-152</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-152">Still NullPointerException in LRUCache (list is null)</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-151"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/bug.gif" alt="Bug" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-151">CACHE-151</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-151">limiting Cache size on disk</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-149"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-149">CACHE-149</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-149">get all values API is missing</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-145"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-145">CACHE-145</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-145">Allow clustered cached to send update notices when a cached object is modified</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-142"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-142">CACHE-142</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-142">DiskPersistenceListener - use properties in cache.path</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-140"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-140">CACHE-140</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-140">Option to avoid bypassing the Cache if browser has resource already</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-123"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-123">CACHE-123</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-123">Provide a "default content" feature instead of the "missing cached content" string</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-121"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-121">CACHE-121</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-121">Need some admin functions from Cache class</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-117"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-117">CACHE-117</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-117">add 'oscache.cluster.group_name" to take care of different clustered-applications</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-100"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-100">CACHE-100</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-100">Provide support for automatic failover to cached version in cache tag</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-81"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-81">CACHE-81</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-81">cache.contains()</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-79"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-79">CACHE-79</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-79">Allow the list of current groups to be obtained from a cache.</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-78"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-78">CACHE-78</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-78">Determining used cache algorithm</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-67"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/improvement.gif" alt="Improvement" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-67">CACHE-67</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-67">On caching pages with session IDs in URLs</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_reopened.gif" alt="Reopened" border="0" />Reopened
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-49"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-49">CACHE-49</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-49">Add Http1.1 Compression (GZip) and increase efficiency</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowNormal">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-21"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-21">CACHE-21</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-21">Cache manager</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+                                    <tr class="rowAlternate">
+                                                            <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-12"><img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/newfeature.gif" alt="New Feature" border="0" /></a>
+                                    </td>
+                                <td nowrap="true">
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-12">CACHE-12</a>
+                                    </td>
+                                <td >
+                                            <a href="http://jira.opensymphony.com/browse/CACHE-12">Accessing caches from outside the Servlet Context</a>
+                                    </td>
+                                <td nowrap="true">
+                                            <img src="http://jira.opensymphony.com/secure/IssueNavigator.jspa?reset=true&pid=10001&fixfor=21470&sorter/field=issuekey&sorter/order=DESC/images/icons/status_open.gif" alt="Open" border="0" />Open
+                                    </td>
+                            </tr>
+            </table>
+</p>
+
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/SVN and Compiling OSCache.html b/docs/wiki/SVN and Compiling OSCache.html
new file mode 100644
index 0000000..29d93f9
--- /dev/null
+++ b/docs/wiki/SVN and Compiling OSCache.html	
@@ -0,0 +1,37 @@
+<html>
+    <head>
+        <title>OSCache - 
+         and Compiling OSCache
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="SVNandCompilingOSCache-SVN"></a>SVN</h3>
+
+<p>The OSCache SVN repository is hosted at <a href="http://svn.opensymphony.com/svn/oscache" title="Visit page outside Confluence">http://svn.opensymphony.com/svn/oscache</a>. You can get the sources anonymously by using e.g. <a href="http://subclipse.tigris.org" title="Visit page outside Confluence">Subclipse</a> a <a href="http://subversion.tigris.org" title="Visit page outside Confluence">Subversion</a> Eclipse Plugin.</p>
+
+<p>If you want to build OSCache from SVN, you have to checkout the project <a href="http://svn.opensymphony.com/svn/opensymphony/trunk/" title="Visit page outside Confluence">OpenSymphony</a> also.</p>
+
+<h3><a name="SVNandCompilingOSCache-CompilingOSCache"></a>Compiling OSCache</h3>
+
+<p>Run <tt>build.xml</tt> with <a href="http://ant.apache.org/" title="Visit page outside Confluence">Ant</a> 1.6.5 (or higher) under Java 1.4 or later. From the OSCache directory, type</p>
+
+<ul>
+	<li>ant - to build the oscache.jar</li>
+	<li>ant dist - to build the complete distribution (only for full SVN checkout)</li>
+	<li>ant clean - to clean up</li>
+</ul>
+
+
+<p>You may need to add the <a href="http://www.jayasoft.org/ivy" title="Visit page outside Confluence">Ivy</a> jar to your $ANT_HOME/lib directory if it is not there already.</p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Spring.html b/docs/wiki/Spring.html
new file mode 100644
index 0000000..f2d643f
--- /dev/null
+++ b/docs/wiki/Spring.html
@@ -0,0 +1,60 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Spring
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h2><a name="Spring-Configuringa%7B%7BGeneralCacheAdministrator%7D%7D"></a>Configuring a <tt>GeneralCacheAdministrator</tt></h2>
+
+<p>A <a href="API Usage.html" title="API Usage">GeneralCacheAdministrator</a> instance that picks up configuration from an <a href="Configuration.html" title="Configuration">oscache.properties</a> file can be configured within Spring using the following code:</p>
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><bean id=<span class="code-quote">"cacheAdministrator"</span> class=<span class="code-quote">"com.opensymphony.oscache.general.GeneralCacheAdministrator"</span> destroy-method=<span class="code-quote">"destroy"</span>/></span></pre>
+</div></div>
+<p>Notice that a <tt>destory-method</tt> is configured to ensure that the <tt>GeneralCacheAdministrator</tt> is closed down gracefully.</p>
+
+<p>If you'd prefer to keep all your configuration inside the Spring configuration, you can omit the <tt>oscache.properties</tt> file and pass in any properties you want to the <tt>GeneralCacheAdministrator</tt> constructor like so:</p>
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><bean id=<span class="code-quote">"cacheAdministrator"</span> class=<span class="code-quote">"com.opensymphony.oscache.general.GeneralCacheAdministrator"</span> destroy-method=<span class="code-quote">"destroy"</span>></span>
+    <span class="code-tag"><constructor-arg index=<span class="code-quote">"0"</span>></span>
+        <span class="code-tag"><props></span>
+            <span class="code-tag"><prop key=<span class="code-quote">"cache.memory"</span>></span>true<span class="code-tag"></prop></span>
+        <span class="code-tag"></props></span>
+    <span class="code-tag"></constructor-arg></span>
+<span class="code-tag"></bean></span></pre>
+</div></div>
+
+<h2><a name="Spring-ConfiguringaCache"></a>Configuring a Cache</h2>
+
+<p>You can configure a <tt>Cache</tt> instance directly using the following snippet of code:</p>
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><bean id=<span class="code-quote">"cache"</span> class=<span class="code-quote">"com.opensymphony.oscache.base.Cache"</span>></span>
+    <span class="code-tag"><constructor-arg index=<span class="code-quote">"0"</span>></span>
+        <span class="code-tag"><value></span>true<span class="code-tag"></value></span> <span class="code-tag"><span class="code-comment"><!-- useMemoryCaching --></span></span>
+    <span class="code-tag"><constructor-arg></span>
+    <span class="code-tag"><constructor-arg index=<span class="code-quote">"1"</span>></span>
+        <span class="code-tag"><value></span>true<span class="code-tag"></value></span> <span class="code-tag"><span class="code-comment"><!-- unlimitedDiskCache --></span></span>
+    <span class="code-tag"><constructor-arg></span>
+    <span class="code-tag"><constructor-arg index=<span class="code-quote">"2"</span>></span>
+        <span class="code-tag"><value></span>true<span class="code-tag"></value></span> <span class="code-tag"><span class="code-comment"><!-- overflowPersistence --></span></span>
+    <span class="code-tag"><constructor-arg></span>
+<span class="code-tag"></bean></span></pre>
+</div></div>
+<p>Alternatively, you can pick up the <tt>Cache</tt> from the <tt>GeneralCacheAdministrator</tt> like so:</p>
+<div class="code"><div class="codeContent">
+<pre class="code-xml"><span class="code-tag"><bean id=<span class="code-quote">"cacheAdministrator"</span> class=<span class="code-quote">"com.opensymphony.oscache.general.GeneralCacheAdministrator"</span> destroy-method=<span class="code-quote">"destroy"</span>/></span>
+
+<span class="code-tag"><bean id=<span class="code-quote">"cache"</span> factory-bean=<span class="code-quote">"cacheAdministrator"</span> factory-method=<span class="code-quote">"getCache"</span>/></span></pre>
+</div></div>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Statistics.html b/docs/wiki/Statistics.html
new file mode 100644
index 0000000..118733e
--- /dev/null
+++ b/docs/wiki/Statistics.html
@@ -0,0 +1,249 @@
+<html>
+    <head>
+        <title>OSCache - 
+        Statistics
+         </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <h3><a name="Statistics-Description"></a>Description</h3>
+
+<p>With the <a href="http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/base/events/package-summary.html" title="Visit page outside Confluence">cache event handlers</a> a listerner can be implemented to provide cache hits and misses information. You can copy and paste the following code to get a statistic of your OSCache integration. Just change the used logger and the sample helps you to improve the cache key creation and to decide which scope to use. The <em>SimpleStatist [...]
+
+<h3><a name="Statistics-SampleCode"></a>Sample Code</h3>
+
+<div class="code" style="border-style: solid; "><div class="codeHeader" style="border-bottom-style: solid; "><b>SimpleStatisticListenerImpl.java</b></div><div class="codeContent">
+<pre class="code-java">/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+<span class="code-keyword">package</span> com.opensymphony.oscache.extra;
+
+<span class="code-keyword">import</span> org.apache.commons.logging.Log;
+<span class="code-keyword">import</span> org.apache.commons.logging.LogFactory;
+
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.Cache;
+<span class="code-keyword">import</span> com.opensymphony.oscache.base.events.*;
+
+/**
+ * A simple implementation of a statistic reporter which uses the
+ * CacheMapAccessEventListener, CacheEntryEventListener and ScopeEventListener.
+ * It uses the events to count the cache hit and misses and of course the
+ * flushes.
+ * <p>
+ * We are not using any <span class="code-keyword">synchronized</span> so that <span class="code-keyword">this</span> does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ */
+<span class="code-keyword">public</span> class SimpleStatisticListenerImpl <span class="code-keyword">implements</span> CacheMapAccessEventListener, CacheEntryEventListener, ScopeEventListener {
+
+    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">transient</span> <span class="code-keyword">final</span> Log log = LogFactory.getLog(SimpleStatisticListenerImpl.class);
+
+    /**
+     * Hit counter
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> hitCount = 0;
+
+    /**
+     * Miss counter
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> missCount = 0;
+
+    /**
+     * Stale hit counter
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> staleHitCount = 0;
+
+    /**
+     * Hit counter sum
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> hitCountSum = 0;
+
+    /**
+     * Miss counter sum
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> missCountSum = 0;
+
+    /**
+     * Stale hit counter
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> staleHitCountSum = 0;
+
+    /**
+     * Flush hit counter
+     */
+    <span class="code-keyword">private</span> <span class="code-object">int</span> flushCount = 0;
+
+    /**
+     * Constructor, empty <span class="code-keyword">for</span> us
+     */
+    <span class="code-keyword">public</span> SimpleStatisticListenerImpl() {
+        log.info(<span class="code-quote">"Creation of SimpleStatisticListenerImpl"</span>);
+    }
+
+    /**
+     * This method handles an event each time the cache is accessed
+     * 
+     * @param event The event triggered when the cache was accessed
+     * @see com.opensymphony.oscache.base.events.CacheMapAccessEventListener#accessed(CacheMapAccessEvent)
+     */
+    <span class="code-keyword">public</span> void accessed(CacheMapAccessEvent event) {
+        <span class="code-object">String</span> result = <span class="code-quote">"N/A"</span>;
+
+        <span class="code-comment">// Retrieve the event type and update the counters
+</span>        CacheMapAccessEventType type = event.getEventType();
+
+        <span class="code-comment">// Handles a hit event
+</span>        <span class="code-keyword">if</span> (type == CacheMapAccessEventType.HIT) {
+            hitCount++;
+            result = <span class="code-quote">"HIT"</span>;
+        }
+        <span class="code-comment">// Handles a stale hit event
+</span>        <span class="code-keyword">else</span> <span class="code-keyword">if</span> (type == CacheMapAccessEventType.STALE_HIT) {
+            staleHitCount++;
+            result = <span class="code-quote">"STALE HIT"</span>;
+        }
+        <span class="code-comment">// Handles a miss event
+</span>        <span class="code-keyword">else</span> <span class="code-keyword">if</span> (type == CacheMapAccessEventType.MISS) {
+            missCount++;
+            result = <span class="code-quote">"MISS"</span>;
+        }
+
+        <span class="code-keyword">if</span> (log.isDebugEnabled()) {
+            log.debug(<span class="code-quote">"ACCESS : "</span> + result + <span class="code-quote">": "</span> + event.getCacheEntryKey());
+            log.debug(<span class="code-quote">"STATISTIC : Hit = "</span> + hitCount + <span class="code-quote">", stale hit ="</span>
+                    + staleHitCount + <span class="code-quote">", miss = "</span> + missCount);
+        }
+    }
+    
+    /**
+     * Logs the flush of the cache.
+     * 
+     * @param info the string to be logged.
+     */
+    <span class="code-keyword">private</span> void flushed(<span class="code-object">String</span> info) {
+        flushCount++;
+
+        hitCountSum += hitCount;
+        staleHitCountSum += staleHitCount;
+        missCountSum += missCount;
+
+        <span class="code-keyword">if</span> (log.isInfoEnabled()) {
+            log.info(<span class="code-quote">"FLUSH : "</span> + info);
+            log.info(<span class="code-quote">"STATISTIC SUM : "</span> + <span class="code-quote">"Hit = "</span> + hitCountSum
+                    + <span class="code-quote">", stale hit = "</span> + staleHitCountSum + <span class="code-quote">", miss = "</span>
+                    + missCountSum + <span class="code-quote">", flush = "</span> + flushCount);
+        }
+
+        hitCount = 0;
+        staleHitCount = 0;
+        missCount = 0;
+    }
+
+    /**
+     * Event fired when a specific or all scopes are flushed.
+     * 
+     * @param event ScopeEvent
+     * @see com.opensymphony.oscache.base.events.ScopeEventListener#scopeFlushed(ScopeEvent)
+     */
+    <span class="code-keyword">public</span> void scopeFlushed(ScopeEvent event) {
+        flushed(<span class="code-quote">"scope "</span> + ScopeEventListenerImpl.SCOPE_NAMES[event.getScope()]);
+    }
+
+    /**
+     * Event fired when an entry is added to the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryAdded(CacheEntryEvent)
+     */
+    <span class="code-keyword">public</span> void cacheEntryAdded(CacheEntryEvent event) {
+        <span class="code-comment">// <span class="code-keyword">do</span> nothing
+</span>    }
+
+    /**
+     * Event fired when an entry is flushed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryFlushed(CacheEntryEvent)
+     */
+    <span class="code-keyword">public</span> void cacheEntryFlushed(CacheEntryEvent event) {
+        <span class="code-comment">// <span class="code-keyword">do</span> nothing, because a group or other flush is coming
+</span>        <span class="code-keyword">if</span> (!Cache.NESTED_EVENT.equals(event.getOrigin())) {
+            flushed(<span class="code-quote">"entry "</span> + event.getKey() + <span class="code-quote">" / "</span> + event.getOrigin());
+        }
+    }
+
+    /**
+     * Event fired when an entry is removed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryRemoved(CacheEntryEvent)
+     */
+    <span class="code-keyword">public</span> void cacheEntryRemoved(CacheEntryEvent event) {
+        <span class="code-comment">// <span class="code-keyword">do</span> nothing
+</span>    }
+
+    /**
+     * Event fired when an entry is updated in the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryUpdated(CacheEntryEvent)
+     */
+    <span class="code-keyword">public</span> void cacheEntryUpdated(CacheEntryEvent event) {
+        <span class="code-comment">// <span class="code-keyword">do</span> nothing
+</span>    }
+
+    /**
+     * Event fired when a group is flushed from the cache.
+     * 
+     * @param event CacheGroupEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheGroupFlushed(CacheGroupEvent)
+     */
+    <span class="code-keyword">public</span> void cacheGroupFlushed(CacheGroupEvent event) {
+        flushed(<span class="code-quote">"group "</span> + event.getGroup());
+    }
+
+    /**
+     * Event fired when a key pattern is flushed from the cache.
+     * 
+     * @param event CachePatternEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cachePatternFlushed(CachePatternEvent)
+     */
+    <span class="code-keyword">public</span> void cachePatternFlushed(CachePatternEvent event) {
+        flushed(<span class="code-quote">"pattern "</span> + event.getPattern());
+    }
+
+    /**
+     * An event that is fired when an entire cache gets flushed.
+     * 
+     * @param event CachewideEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheFlushed(CachewideEvent)
+     */
+    <span class="code-keyword">public</span> void cacheFlushed(CachewideEvent event) {
+        flushed(<span class="code-quote">"wide "</span> + event.getDate());
+    }
+
+    /**
+     * Return the counters in a string form
+     *
+     * @<span class="code-keyword">return</span> <span class="code-object">String</span>
+     */
+    <span class="code-keyword">public</span> <span class="code-object">String</span> toString() {
+        <span class="code-keyword">return</span> <span class="code-quote">"SimpleStatisticListenerImpl: Hit = "</span> + hitCount + <span class="code-quote">" / "</span> + hitCountSum
+                + <span class="code-quote">", stale hit = "</span> + staleHitCount + <span class="code-quote">" / "</span> + staleHitCountSum
+                + <span class="code-quote">", miss = "</span> + missCount + <span class="code-quote">" / "</span> + missCountSum
+                + <span class="code-quote">", flush = "</span> + flushCount;
+    }
+}</pre>
+</div></div> 
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/Statistics_attachments/StatisticListenerImpl.java b/docs/wiki/Statistics_attachments/StatisticListenerImpl.java
new file mode 100644
index 0000000..263114d
--- /dev/null
+++ b/docs/wiki/Statistics_attachments/StatisticListenerImpl.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.events.*;
+
+/**
+ * A simple implementation of a statistic reporter which uses the
+ * CacheMapAccessEventListener, CacheEntryEventListener and ScopeEventListener.
+ * It uses the events to count the cache hit and misses and of course the
+ * flushes.
+ * <p>
+ * We are not using any synchronized so that this does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ */
+public class StatisticListenerImpl implements CacheMapAccessEventListener, CacheEntryEventListener, ScopeEventListener {
+
+    private static transient final Log log = LogFactory.getLog(StatisticListenerImpl.class);
+
+    /**
+     * Hit counter
+     */
+    private int hitCount = 0;
+
+    /**
+     * Miss counter
+     */
+    private int missCount = 0;
+
+    /**
+     * Stale hit counter
+     */
+    private int staleHitCount = 0;
+
+    /**
+     * Hit counter sum
+     */
+    private int hitCountSum = 0;
+
+    /**
+     * Miss counter sum
+     */
+    private int missCountSum = 0;
+
+    /**
+     * Stale hit counter
+     */
+    private int staleHitCountSum = 0;
+
+    /**
+     * Flush hit counter
+     */
+    private int flushCount = 0;
+
+    /**
+     * Constructor, empty for us
+     */
+    public StatisticListenerImpl() {
+        log.info("Creation of StatisticListenerImpl");
+    }
+
+    /**
+     * This method handles an event each time the cache is accessed
+     * 
+     * @param event The event triggered when the cache was accessed
+     * @see com.opensymphony.oscache.base.events.CacheMapAccessEventListener#accessed(CacheMapAccessEvent)
+     */
+    public void accessed(CacheMapAccessEvent event) {
+        String result = "N/A";
+
+        // Retrieve the event type and update the counters
+        CacheMapAccessEventType type = event.getEventType();
+
+        // Handles a hit event
+        if (type == CacheMapAccessEventType.HIT) {
+            hitCount++;
+            result = "HIT";
+        }
+        // Handles a stale hit event
+        else if (type == CacheMapAccessEventType.STALE_HIT) {
+            staleHitCount++;
+            result = "STALE HIT";
+        }
+        // Handles a miss event
+        else if (type == CacheMapAccessEventType.MISS) {
+            missCount++;
+            result = "MISS";
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("ACCESS : " + result + ": " + event.getCacheEntryKey());
+            log.debug("STATISTIC : Hit = " + hitCount + ", stale hit ="
+                    + staleHitCount + ", miss = " + missCount);
+        }
+    }
+    
+    /**
+     * Logs the flush of the cache.
+     * 
+     * @param info the string to be logged.
+     */
+    private void flushed(String info) {
+        flushCount++;
+
+        hitCountSum += hitCount;
+        staleHitCountSum += staleHitCount;
+        missCountSum += missCount;
+
+        if (log.isInfoEnabled()) {
+            log.info("FLUSH : " + info);
+            log.info("STATISTIC SUM : " + "Hit = " + hitCountSum
+                    + ", stale hit = " + staleHitCountSum + ", miss = "
+                    + missCountSum + ", flush = " + flushCount);
+        }
+
+        hitCount = 0;
+        staleHitCount = 0;
+        missCount = 0;
+    }
+
+    /**
+     * Event fired when a specific or all scopes are flushed.
+     * 
+     * @param event ScopeEvent
+     * @see com.opensymphony.oscache.base.events.ScopeEventListener#scopeFlushed(ScopeEvent)
+     */
+    public void scopeFlushed(ScopeEvent event) {
+        flushed("scope " + ScopeEventListenerImpl.SCOPE_NAMES[event.getScope()]);
+    }
+
+    /**
+     * Event fired when an entry is added to the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryAdded(CacheEntryEvent)
+     */
+    public void cacheEntryAdded(CacheEntryEvent event) {
+        // do nothing
+    }
+
+    /**
+     * Event fired when an entry is flushed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryFlushed(CacheEntryEvent)
+     */
+    public void cacheEntryFlushed(CacheEntryEvent event) {
+        // do nothing, because a group or other flush is coming
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin())) {
+            flushed("entry " + event.getKey() + " / " + event.getOrigin());
+        }
+    }
+
+    /**
+     * Event fired when an entry is removed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryRemoved(CacheEntryEvent)
+     */
+    public void cacheEntryRemoved(CacheEntryEvent event) {
+        // do nothing
+    }
+
+    /**
+     * Event fired when an entry is updated in the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryUpdated(CacheEntryEvent)
+     */
+    public void cacheEntryUpdated(CacheEntryEvent event) {
+        // do nothing
+    }
+
+    /**
+     * Event fired when a group is flushed from the cache.
+     * 
+     * @param event CacheGroupEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheGroupFlushed(CacheGroupEvent)
+     */
+    public void cacheGroupFlushed(CacheGroupEvent event) {
+        flushed("group " + event.getGroup());
+    }
+
+    /**
+     * Event fired when a key pattern is flushed from the cache.
+     * 
+     * @param event CachePatternEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cachePatternFlushed(CachePatternEvent)
+     */
+    public void cachePatternFlushed(CachePatternEvent event) {
+        flushed("pattern " + event.getPattern());
+    }
+
+    /**
+     * An event that is fired when an entire cache gets flushed.
+     * 
+     * @param event CachewideEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheFlushed(CachewideEvent)
+     */
+    public void cacheFlushed(CachewideEvent event) {
+        flushed("wide " + event.getDate());
+    }
+
+    /**
+     * Return the counters in a string form
+     *
+     * @return String
+     */
+    public String toString() {
+        return "StatisticListenerImpl: Hit = " + hitCount + " / " + hitCountSum
+                + ", stale hit = " + staleHitCount + " / " + staleHitCountSum
+                + ", miss = " + missCount + " / " + missCountSum + ", flush = "
+                + flushCount;
+    }
+}
\ No newline at end of file
diff --git a/docs/wiki/What is OSCache.html b/docs/wiki/What is OSCache.html
new file mode 100644
index 0000000..2f49be1
--- /dev/null
+++ b/docs/wiki/What is OSCache.html	
@@ -0,0 +1,55 @@
+<html>
+    <head>
+        <title>OSCache - 
+         is OSCache
+        </title>
+	    <link rel="stylesheet" href="styles/site.css" type="text/css" />
+        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    </head>
+
+    <body>
+	    <table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff">
+		    <tr>
+			    <td valign="top" class="pagebody">
+				    <p><b>OSCache</b> is a widely used, high performance J2EE caching framework.</p>
+
+<div class="panel" style="border-style: solid; "><div class="panelHeader" style="border-bottom-style: solid; "><b>The Problems Solved</b></div><div class="panelContent">
+<p><b>OSCache</b> solves fundamental problems for dynamic websites:</p>
+
+<ol>
+	<li><b>Caching Dynamic Content</b> - Dynamic content of some form must often be executed during each request, but sometimes that content doesn't change every request. Caching the whole page does not help because sections of the page change every request.
+	<ul>
+		<li><b>OSCache</b> solves this problem by providing a means to cache sections of JSP pages.</li>
+	</ul>
+	</li>
+	<li><b>Caching Binary Content</b> - Generated images and PDFs can be very costly in terms of server load.
+	<ul>
+		<li><b>OSCache</b> solves this problem through a Servlet 2.3 CachingFilter which can cache any URI (such as an entire page or a generated image/PDF)</li>
+	</ul>
+	</li>
+	<li><b>Error Tolerance</b> - If one error occurs somewhere on your dynamic page, chances are the whole page will be returned as an error, even if 95% of the page executed correctly.
+	<ul>
+		<li><b>OSCache</b> solves this problem by allowing you to serve the cached content in the event of an error, and then reporting the error appropriately.</li>
+	</ul>
+	</li>
+</ol>
+</div></div>
+
+<div class="panel" style="border-style: solid; "><div class="panelHeader" style="border-bottom-style: solid; "><b>Brief Feature List</b></div><div class="panelContent">
+<p>In addition to it's servlet-specific features, <b>OSCache</b> can be used as a generic caching solution for any Java application. A few of its generic features include:</p>
+
+<ul>
+	<li><b>Caching of Arbitrary Objects</b> - You are not restricted to caching portions of JSP pages or HTTP requests. Any Java object can be cached.</li>
+	<li><b>Comprehensive API</b> - The <b>OSCache</b> API gives you full programmatic control over all of OSCache's features.</li>
+	<li><b>Persistent Caching</b> - The cache can optionally be disk-based, thereby allowing expensive-to-create data to remain cached even across application restarts.</li>
+	<li><b>Clustering</b> - Support for clustering of cached data can be enabled with a single configuration parameter. No code changes required.</li>
+	<li><b>Expiry of Cache Entries</b> - You have a huge amount of control over how cached objects expire, including pluggable RefreshPolicies if the default functionality does not meet your requirements.</li>
+</ul>
+</div></div>
+<p>We encourage you to take a look at the full <a href="Feature List.html" title="Feature List">Feature List</a> to see what else <b>OSCache</b> has to offer.</p>
+
+                    			    </td>
+		    </tr>
+	    </table>
+    </body>
+</html>
diff --git a/docs/wiki/border/border_bottom.gif b/docs/wiki/border/border_bottom.gif
new file mode 100644
index 0000000..b93012a
Binary files /dev/null and b/docs/wiki/border/border_bottom.gif differ
diff --git a/docs/wiki/border/spacer.gif b/docs/wiki/border/spacer.gif
new file mode 100644
index 0000000..fc25609
Binary files /dev/null and b/docs/wiki/border/spacer.gif differ
diff --git a/docs/wiki/icons/blogentry_16.gif b/docs/wiki/icons/blogentry_16.gif
new file mode 100644
index 0000000..83d600d
Binary files /dev/null and b/docs/wiki/icons/blogentry_16.gif differ
diff --git a/docs/wiki/icons/bullet_blue.gif b/docs/wiki/icons/bullet_blue.gif
new file mode 100644
index 0000000..25bfa0c
Binary files /dev/null and b/docs/wiki/icons/bullet_blue.gif differ
diff --git a/docs/wiki/icons/comment_16.gif b/docs/wiki/icons/comment_16.gif
new file mode 100644
index 0000000..6f76e86
Binary files /dev/null and b/docs/wiki/icons/comment_16.gif differ
diff --git a/docs/wiki/icons/emoticons/add.gif b/docs/wiki/icons/emoticons/add.gif
new file mode 100644
index 0000000..0c00fe0
Binary files /dev/null and b/docs/wiki/icons/emoticons/add.gif differ
diff --git a/docs/wiki/icons/emoticons/biggrin.gif b/docs/wiki/icons/emoticons/biggrin.gif
new file mode 100644
index 0000000..ad04031
Binary files /dev/null and b/docs/wiki/icons/emoticons/biggrin.gif differ
diff --git a/docs/wiki/icons/emoticons/check.gif b/docs/wiki/icons/emoticons/check.gif
new file mode 100644
index 0000000..28bb999
Binary files /dev/null and b/docs/wiki/icons/emoticons/check.gif differ
diff --git a/docs/wiki/icons/emoticons/error.gif b/docs/wiki/icons/emoticons/error.gif
new file mode 100644
index 0000000..6d68a8c
Binary files /dev/null and b/docs/wiki/icons/emoticons/error.gif differ
diff --git a/docs/wiki/icons/emoticons/forbidden.gif b/docs/wiki/icons/emoticons/forbidden.gif
new file mode 100644
index 0000000..c6acdec
Binary files /dev/null and b/docs/wiki/icons/emoticons/forbidden.gif differ
diff --git a/docs/wiki/icons/emoticons/help_16.gif b/docs/wiki/icons/emoticons/help_16.gif
new file mode 100644
index 0000000..83387c2
Binary files /dev/null and b/docs/wiki/icons/emoticons/help_16.gif differ
diff --git a/docs/wiki/icons/emoticons/information.gif b/docs/wiki/icons/emoticons/information.gif
new file mode 100644
index 0000000..072ab66
Binary files /dev/null and b/docs/wiki/icons/emoticons/information.gif differ
diff --git a/docs/wiki/icons/emoticons/lightbulb.gif b/docs/wiki/icons/emoticons/lightbulb.gif
new file mode 100644
index 0000000..cd72409
Binary files /dev/null and b/docs/wiki/icons/emoticons/lightbulb.gif differ
diff --git a/docs/wiki/icons/emoticons/lightbulb_on.gif b/docs/wiki/icons/emoticons/lightbulb_on.gif
new file mode 100644
index 0000000..3768ef7
Binary files /dev/null and b/docs/wiki/icons/emoticons/lightbulb_on.gif differ
diff --git a/docs/wiki/icons/emoticons/sad.gif b/docs/wiki/icons/emoticons/sad.gif
new file mode 100644
index 0000000..6aca310
Binary files /dev/null and b/docs/wiki/icons/emoticons/sad.gif differ
diff --git a/docs/wiki/icons/emoticons/smile.gif b/docs/wiki/icons/emoticons/smile.gif
new file mode 100644
index 0000000..f8dc278
Binary files /dev/null and b/docs/wiki/icons/emoticons/smile.gif differ
diff --git a/docs/wiki/icons/emoticons/star_blue.gif b/docs/wiki/icons/emoticons/star_blue.gif
new file mode 100644
index 0000000..9b366ce
Binary files /dev/null and b/docs/wiki/icons/emoticons/star_blue.gif differ
diff --git a/docs/wiki/icons/emoticons/star_green.gif b/docs/wiki/icons/emoticons/star_green.gif
new file mode 100644
index 0000000..42d27ec
Binary files /dev/null and b/docs/wiki/icons/emoticons/star_green.gif differ
diff --git a/docs/wiki/icons/emoticons/star_red.gif b/docs/wiki/icons/emoticons/star_red.gif
new file mode 100644
index 0000000..c10acb9
Binary files /dev/null and b/docs/wiki/icons/emoticons/star_red.gif differ
diff --git a/docs/wiki/icons/emoticons/star_yellow.gif b/docs/wiki/icons/emoticons/star_yellow.gif
new file mode 100644
index 0000000..0f1d151
Binary files /dev/null and b/docs/wiki/icons/emoticons/star_yellow.gif differ
diff --git a/docs/wiki/icons/emoticons/thumbs_down.gif b/docs/wiki/icons/emoticons/thumbs_down.gif
new file mode 100644
index 0000000..855c748
Binary files /dev/null and b/docs/wiki/icons/emoticons/thumbs_down.gif differ
diff --git a/docs/wiki/icons/emoticons/thumbs_up.gif b/docs/wiki/icons/emoticons/thumbs_up.gif
new file mode 100644
index 0000000..7c55fee
Binary files /dev/null and b/docs/wiki/icons/emoticons/thumbs_up.gif differ
diff --git a/docs/wiki/icons/emoticons/tongue.gif b/docs/wiki/icons/emoticons/tongue.gif
new file mode 100644
index 0000000..fe77df2
Binary files /dev/null and b/docs/wiki/icons/emoticons/tongue.gif differ
diff --git a/docs/wiki/icons/emoticons/warning.gif b/docs/wiki/icons/emoticons/warning.gif
new file mode 100644
index 0000000..1c9883b
Binary files /dev/null and b/docs/wiki/icons/emoticons/warning.gif differ
diff --git a/docs/wiki/icons/emoticons/wink.gif b/docs/wiki/icons/emoticons/wink.gif
new file mode 100644
index 0000000..24bdea9
Binary files /dev/null and b/docs/wiki/icons/emoticons/wink.gif differ
diff --git a/docs/wiki/icons/home_16.gif b/docs/wiki/icons/home_16.gif
new file mode 100644
index 0000000..7a902b1
Binary files /dev/null and b/docs/wiki/icons/home_16.gif differ
diff --git a/docs/wiki/icons/linkext7.gif b/docs/wiki/icons/linkext7.gif
new file mode 100644
index 0000000..f2dd2dc
Binary files /dev/null and b/docs/wiki/icons/linkext7.gif differ
diff --git a/docs/wiki/icons/mail_16.gif b/docs/wiki/icons/mail_16.gif
new file mode 100644
index 0000000..0386c0d
Binary files /dev/null and b/docs/wiki/icons/mail_16.gif differ
diff --git a/docs/wiki/icons/mail_small.gif b/docs/wiki/icons/mail_small.gif
new file mode 100644
index 0000000..a3b7d9f
Binary files /dev/null and b/docs/wiki/icons/mail_small.gif differ
diff --git a/docs/wiki/icons/user_12.gif b/docs/wiki/icons/user_12.gif
new file mode 100644
index 0000000..d41511f
Binary files /dev/null and b/docs/wiki/icons/user_12.gif differ
diff --git a/docs/wiki/icons/user_16.gif b/docs/wiki/icons/user_16.gif
new file mode 100644
index 0000000..f5a2fb6
Binary files /dev/null and b/docs/wiki/icons/user_16.gif differ
diff --git a/docs/wiki/index.html b/docs/wiki/index.html
new file mode 100644
index 0000000..00beaf0
--- /dev/null
+++ b/docs/wiki/index.html
@@ -0,0 +1,733 @@
+<html>
+<head>
+<title>OSCache - Index</title>
+<link rel="stylesheet" href="styles/site.css" type="text/css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+
+<body>
+<table class="pagecontent" border="0" cellpadding="0" cellspacing="0"
+	width="100%" bgcolor="#ffffff">
+	<tr>
+		<td valign="top" class="pagebody">
+		<h2>Space Index</h2>
+
+		<p>
+		<table width="99%" cellspacing="0" class="grid">
+			<tr>
+				<td colspan="2">
+				<table width="100%">
+					<tr>
+						<td><a href="#index-0-9">0-9</a> ... 0</td>
+						<td><a href="#index-A">A</a> ... 1</td>
+						<td><a href="#index-B">B</a> ... 0</td>
+						<td><a href="#index-C">C</a> ... 8</td>
+						<td><a href="#index-D">D</a> ... 1</td>
+						<td><a href="#index-E">E</a> ... 0</td>
+					</tr>
+					<tr>
+						<td><a href="#index-F">F</a> ... 2</td>
+						<td><a href="#index-G">G</a> ... 0</td>
+						<td><a href="#index-H">H</a> ... 7</td>
+						<td><a href="#index-I">I</a> ... 2</td>
+						<td><a href="#index-J">J</a> ... 2</td>
+						<td><a href="#index-K">K</a> ... 0</td>
+					</tr>
+					<tr>
+						<td><a href="#index-L">L</a> ... 1</td>
+						<td><a href="#index-M">M</a> ... 0</td>
+						<td><a href="#index-N">N</a> ... 0</td>
+						<td><a href="#index-O">O</a> ... 32</td>
+						<td><a href="#index-P">P</a> ... 0</td>
+						<td><a href="#index-Q">Q</a> ... 0</td>
+					</tr>
+					<tr>
+						<td><a href="#index-R">R</a> ... 2</td>
+						<td><a href="#index-S">S</a> ... 3</td>
+						<td><a href="#index-T">T</a> ... 0</td>
+						<td><a href="#index-U">U</a> ... 0</td>
+						<td><a href="#index-V">V</a> ... 0</td>
+						<td><a href="#index-W">W</a> ... 1</td>
+					</tr>
+					<tr>
+						<td><a href="#index-X">X</a> ... 0</td>
+						<td><a href="#index-Y">Y</a> ... 0</td>
+						<td><a href="#index-Z">Z</a> ... 0</td>
+						<td><a href="#index-%21@%23%24">!@#$</a> ... 0</td>
+						<td> </td>
+						<td> </td>
+					</tr>
+				</table>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-0-9"></a>0-9</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-A"></a>A</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="API Usage" /> <a
+					href="/display/CACHE/API+Usage">API Usage</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Beside the
+				JSP tag library JSP Tags and the CacheFilter you can use OSCache
+				through its straightforward API. You can use the
+				GeneralCacheAdministrator
+				http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/general/GeneralCacheAdministrator.html
+				to create, flush ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-B"></a>B</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-C"></a>C</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="CacheFilter" /> <a
+					href="/display/CACHE/CacheFilter">CacheFilter</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache
+				comes with a servlet filter that enables you to transparently cache
+				entire pages of your website, and even binary files. Caching of
+				binary files is extremely useful when they are generated
+				dynamically, e.g. PDF files or images. A tutorial CacheFilter
+				Tutorial ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="CacheFilter Tutorial" /> <a
+					href="/display/CACHE/CacheFilter+Tutorial">CacheFilter Tutorial</a>
+				<br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Introduction
+				OSCache comes with a servlet filter that enables you to
+				transparently cache entire pages of your website, and even binary
+				files. Caching of binary files is extremely useful when they are
+				generated dynamically, e.g. PDF files or images. In addition by
+				using ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Chain Caching Model" /> <a
+					href="/display/CACHE/Chain+Caching+Model">Chain Caching Model</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Discussion
+				Lars wrote: Would it be possible to use the intercepting filter
+				pattern to support all possible cache models with a lot of
+				combination capabilities? It's possible to define the capacity for
+				each cache etc. DiskPersistence, SoftReferenceCache etc. would
+				implement the Command interface ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Change Log" /> <a
+					href="/display/CACHE/Change+Log">Change Log</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes 2007 OSCache 2.4.1 OSCache 2.4 2006 OSCache 2.3.2 OSCache
+				2.3.1 OSCache 2.3 2005 OSCache 2.2 OSCache 2.2 RC OSCache 2.1.1
+				OSCache 2.1 2004 OSCache 2.0.2 2003 OSCache 2.0.1 ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Clustering" /> <a
+					href="/display/CACHE/Clustering">Clustering</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">New in
+				OSCache 2.0 is support for clustering of caches. OSCache currently
+				ships with implementations that allow you to use either JavaGroups
+				or JMS as the underlying broadcast protocol. Caches across a cluster
+				only broadcast messages when flush events occur. This means ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Complete Change Log" /> <a
+					href="/display/CACHE/Complete+Change+Log">Complete Change Log</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache
+				2.4.1 OSCache 2.4 OSCache 2.3.2 OSCache 2.3.1 OSCache 2.3 OSCache
+				2.2 Final OSCache 2.2 RC OSCache 2.1.1 OSCache 2.1 OSCache 2.0.2
+				OSCache 2.0.1 OSCache 2.0 OSCache 2.0 beta 2 ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Configuration" /> <a
+					href="/display/CACHE/Configuration">Configuration</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">guide only
+				covers the configuration of OSCache by using the oscache.properties
+				file. To see how to install OSCache and where to place the
+				oscache.properties file, see the Installation Guide. The following
+				properties are able to be set in the oscache.properties file:
+				cache.memory Valid values are true or false ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Cron Expressions" /> <a
+					href="/display/CACHE/Cron+Expressions">Cron Expressions</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Prior to
+				version 2.0 of OSCache, content expiry could only be specified in
+				terms of how long a piece of content had been in the cache, ie, it
+				was based on the age of the content. If you needed to expire it at a
+				particular time of day or on a specific date, you ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-D"></a>D</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Documentation" /> <a
+					href="/display/CACHE/Documentation">Documentation</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">1. Overview
+				# What is OSCache # License # Feature List Details on OSCache's
+				features and how they are best used. # Requirements What is required
+				to run OSCache. # FAQ Frequently Asked Questions about OSCache. #
+				OSCache in the Wild A list of sites that are using OSCache ...</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-E"></a>E</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-F"></a>F</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="FAQ" /> <a
+					href="/display/CACHE/FAQ">FAQ</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Got a
+				question you'd like to ask? Ask us and we'll add it to the FAQ.
+				Questions What can I use OSCache for exactly? #uses Where is the
+				data cached? #data Can OSCache cache Java objects rather than
+				portions ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Feature List" /> <a
+					href="/display/CACHE/Feature+List">Feature List</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache
+				Features Fast inmemory caching OSCache allows you to store dynamic
+				content (eg for 30 minutes) in memory. Each further request is
+				served directly from the memory cache, resulting in dramatic speed
+				increases. The cache is keyed programmatically ...</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-G"></a>G</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-H"></a>H</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Hibernate" /> <a
+					href="/display/CACHE/Hibernate">Hibernate</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Hibernate
+				http://www.hibernate.org/ is a powerful, ultrahigh performance
+				object/relational persistence and query service for Java. Hibernate
+				lets you develop persistent objects following common Java idiom
+				including association, inheritance, polymorphism, composition and
+				the Java collections framework ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle"
+					title="Hibernate 2.1 and pre OSCache 2.4 support" /> <a
+					href="/display/CACHE/Hibernate+2.1+and+pre+OSCache+2.4+support">Hibernate
+				2.1 and pre OSCache 2.4 support</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">page is
+				intended to give integration support for Hibernate 2.1 and for pre
+				OSCache 2.4 releases. It's recommended to use the new Hibernate 3.2
+				Hibernate classes. Hibernate http://www.hibernate.org/ is a
+				powerful, ultrahigh performance object/relational persistence and
+				query service for Java ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Hibernate 2.1 Cache Adapter" />
+				<a href="/display/CACHE/Hibernate+2.1+Cache+Adapter">Hibernate
+				2.1 Cache Adapter</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Patched
+				version of OSCache.java originally created by Mathias Bogaert.
+				import java.util.Properties; import net.sf.hibernate.cache.Cache;
+				import net.sf.hibernate.cache.CacheException; import
+				net.sf.hibernate.cache.Timestamper; import
+				net.sf.hibernate.util.PropertiesHelper; import
+				net.sf.hibernate.util.StringHelper; import
+				com.opensymphony.oscache.base.Config; import
+				com.opensymphony.oscache.base.CacheEntry; import
+				com.opensymphony.oscache.base.NeedsRefreshException; import
+				com.opensymphony.oscache.general.GeneralCacheAdministrator; /
+				Adapter for the OSCache implementation ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Hibernate 2.1 Cache Provider" />
+				<a href="/display/CACHE/Hibernate+2.1+Cache+Provider">Hibernate
+				2.1 Cache Provider</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Patched
+				version of OSCacheProvider.java originally created by Mathias
+				Bogaert. import java.util.Properties; import
+				net.sf.hibernate.cache.Cache; import
+				net.sf.hibernate.cache.CacheException; import
+				net.sf.hibernate.cache.CacheProvider; import
+				net.sf.hibernate.cache.Timestamper; import
+				net.sf.hibernate.util.PropertiesHelper; import
+				net.sf.hibernate.util.StringHelper; import
+				com.opensymphony.oscache.base.CacheEntry; import
+				com.opensymphony.oscache.base.Config; / Support for OpenSymphony
+				OSCache. This implementation assumes ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Hibernate 3 Cache Adaptor" /> <a
+					href="/display/CACHE/Hibernate+3+Cache+Adaptor">Hibernate 3
+				Cache Adaptor</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Patched
+				version of OSCache.java for Hibernate 3 originally created by
+				Mathias Bogaert. import java.util.Properties; import java.util.Map;
+				import org.hibernate.util.PropertiesHelper; import
+				org.hibernate.util.StringHelper; import org.hibernate.cache.; import
+				com.opensymphony.oscache.base.Config; import
+				com.opensymphony.oscache.base.CacheEntry; import
+				com.opensymphony.oscache.base.NeedsRefreshException; import
+				com.opensymphony.oscache.general.GeneralCacheAdministrator; /
+				Adapter for the OSCache implementation ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Hibernate 3 Cache Provider" />
+				<a href="/display/CACHE/Hibernate+3+Cache+Provider">Hibernate 3
+				Cache Provider</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Patched
+				version of OSCacheProvider.java for Hibernate 3.0 originally created
+				by Mathias Bogaert. import java.util.Properties; import
+				org.hibernate.util.PropertiesHelper; import
+				org.hibernate.util.StringHelper; import org.hibernate.cache.; import
+				com.opensymphony.oscache.base.CacheEntry; import
+				com.opensymphony.oscache.base.Config; / Support for OpenSymphony
+				OSCache. This implementation assumes that identifiers have
+				wellbehaved <tt... </div>
+				<img src="/images/icons/home_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Home (Space Home Page)" /> <a
+					href="/display/CACHE/Home">Home</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Welcome to
+				the OSCache wiki. OSCache is a caching solution that includes a JSP
+				tag library and set of classes to perform fine grained dynamic
+				caching of JSP content, servlet responses or arbitrary objects. It
+				provides both in memory and persistent on disk caches, and can ...</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-I"></a>I</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Index" /> <a
+					href="/display/CACHE/Index">Index</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px"></div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Installation Guide" /> <a
+					href="/display/CACHE/Installation+Guide">Installation Guide</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">installation
+				guide shows you how to configure OSCache 2.4 for use inside your JSP
+				pages. It assumes you have downloaded the latest version
+				https://oscache.dev.java.net/servlets/ProjectDocumentList, which
+				requires at least Java 1.4 and a Servlet 2.3 ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-J"></a>J</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="JMX Monitoring" /> <a
+					href="/display/CACHE/JMX+Monitoring">JMX Monitoring</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">New in
+				OSCache 2.4 is support for JMX monitoring and administration via the
+				Spring Framework http://www.springframework.org. In
+				oscache.properties, enable the statistic listener:
+				cache.event.listeners=
+				com.opensymphony.oscache.extra.StatisticListenerImpl Then add this
+				to the Spring application context <! create mbeanserver, this doesn't need to be done if running on an Appserver ... </div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="JSP Tags" /> <a
+					href="/display/CACHE/JSP+Tags">JSP Tags</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache
+				comes with a JSP tag library that controls all its major functions.
+				The tags are listed below with descriptions, attributes and examples
+				of use. For instructions on installing OSCache in a web application,
+				see the Installation Guide. You just have to add the following ...</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-K"></a>K</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-L"></a>L</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="License" /> <a
+					href="/display/CACHE/License">License</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">All
+				OpenSymphony projects use the OpenSymphony License, which is a
+				modified Apache License. You can find the license at
+				http://www.opensymphony.com/oscache/license.action</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-M"></a>M</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-N"></a>N</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-O"></a>O</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.0 beta 0" /> <a
+					href="/display/CACHE/OSCache+1.0+beta+0">OSCache 1.0 beta 0</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (26th November, 2000 by Mike CannonBrookes,
+				mike at atlassian.com) Initial release of OSCache Conceptualised a few
+				things I've been working on over the past month. Added persistent on
+				disk caching and error tolerance (through <usecached /> tag</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.0 beta 1" /> <a
+					href="/display/CACHE/OSCache+1.0+beta+1">OSCache 1.0 beta 1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (20th February, 2001 by Mike CannonBrookes,
+				mike at atlassian.com) Fixed a few bugs. Greatest of which there is no
+				longer a disk leakage from session caches on disk. Also session
+				caching bugs fixed, usecached bugs fixed lots of work ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.0 beta 2" /> <a
+					href="/display/CACHE/OSCache+1.0+beta+2">OSCache 1.0 beta 2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (20th March, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Fixed more bugs. Moved things around so that the CacheAdministrator
+				has more functionality and is now a Singleton (per web app context).
+				This means no more depedency on ServletContextListener to start ...
+				</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.1" /> <a
+					href="/display/CACHE/OSCache+1.1">OSCache 1.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (25th March, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Moved up to 1.1 because a lot of documentation improving and some
+				small bug fixing has been done Javadocs should now be very readable
+				for all classes and methods ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.2" /> <a
+					href="/display/CACHE/OSCache+1.2">OSCache 1.2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (28th March, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Fixed a large bug that resulted in CacheEntry's not refreshing.
+				Large enough in a Caching library to demand a new point release</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.2.1" /> <a
+					href="/display/CACHE/OSCache+1.2.1">OSCache 1.2.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (10th May, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Speed improvements in CacheEntry and CacheAdministrator (by Kesav
+				Kumar kesavk at voquette.com) Fixed DOCTYPE in taglib.tld (also Kesav!)
+				Removed backup / swap / temp files from zip (and changed build file</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.2.5" /> <a
+					href="/display/CACHE/OSCache+1.2.5">OSCache 1.2.5</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (18th May, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Added ability to turn off file caching (just remove or comment out
+				cache.properties) Removed a pesky (but ineffectual) bug where
+				session caches being removed from disk ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.3" /> <a
+					href="/display/CACHE/OSCache+1.3">OSCache 1.3</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (9th June, 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Fixed a single bug in the file caching should now work Added
+				property to set the cache key (not sure if this is useful) Cleaned
+				up a lot of the code, refactored slightly so ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.5" /> <a
+					href="/display/CACHE/OSCache+1.5">OSCache 1.5</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (6th August, 2001 by Todd Gochenour, tgochenour at peregrine.com)
+				Added boolean "cache.memory" attribute to oscache.properties to
+				eliminate memory consumption and rely strictly on disk storage.
+				Added three interfaces "CacheLog", "CacheProperties", and
+				"CacheContents" to allow plugable implementations for these
+				functions ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.6" /> <a
+					href="/display/CACHE/OSCache+1.6">OSCache 1.6</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (5th September, 2001 by Mike CannonBrookes,
+				mike at atlassian.com) Changed the CacheEntry so that it caches Object
+				rather than String (allowing image caching) (Serge Knystautas,
+				sergek at lokitech.com) Cached objects are now serialized to disk so
+				cannot be read ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.6.1" /> <a
+					href="/display/CACHE/OSCache+1.6.1">OSCache 1.6.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (16th September, 2001 by Todd Gochenour,
+				tgochenour at peregrine.com) Removed attribute "encoding" in all areas,
+				since Object serialization stores strings in UTF8 format, encoding
+				is no longer necessary. Added Synchronization to getCacheEntry() to
+				insure multiple threads do ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.0" /> <a
+					href="/display/CACHE/OSCache+1.7.0">OSCache 1.7.0</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (26th September 2001 by Francois Beauregard,
+				fbeauregard at pyxistech.com, and Alain Bergevin,
+				abergevin at pyxistech.com, of Pyxis Technologies Inc.) This version
+				include some refactoring, corrections and new features. Here are the
+				highlights: CacheAdministrator has been split in 3. We have ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.1" /> <a
+					href="/display/CACHE/OSCache+1.7.1">OSCache 1.7.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (26th September 2001 by Francois Beauregard,
+				fbeauregard at pyxistech.com, and Alain Bergevin,
+				abergevin at pyxistech.com, of Pyxis Technologies Inc.) Cache Events
+				Persistence mechanism refactored Cache Algorithms FIFO LRU (Limit
+				the size of the cache) AbstractConcurrentReadCache from Doug Lea's
+				...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.2" /> <a
+					href="/display/CACHE/OSCache+1.7.2">OSCache 1.7.2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (31st October 2001 by Mike CannonBrookes, mike at atlassian.com)
+				Cleaned up all JavaDoc messages to ensure consistency and
+				readability (removed unnecessary CVS tags, added <code> and <pre> where needed, added @return and @param to all methods ... </div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.3" /> <a
+					href="/display/CACHE/OSCache+1.7.3">OSCache 1.7.3</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (11th November 2001 by Francois Beauregard,
+				fbeauregard at pyxistech.com) TestCacheEntry had a test method with
+				improper name (flush > testFlush) Pluggable entry refresh policy now
+				available in the cache tag</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.4" /> <a
+					href="/display/CACHE/OSCache+1.7.4">OSCache 1.7.4</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (3rd December 2001 by Francois Beauregard,
+				fbeauregard at pyxistech.com, and Mike CannonBrookes,
+				mike at atlassian.com) Made all servlet cache components serializable
+				(fixes bug reported on list with JRun</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 1.7.5" /> <a
+					href="/display/CACHE/OSCache+1.7.5">OSCache 1.7.5</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (5th January 2002 by Mike CannonBrookes, mike at atlassian.com)
+				Fixed up logging system slightly. All errors should now be logged
+				with logError() and normal messages with log() Fixed bug in build
+				file which put oscache.properties inside ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.0" /> <a
+					href="/display/CACHE/OSCache+2.0">OSCache 2.0</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (22nd September 2003 by Chris Miller) Improvements: Minor
+				FastCronParser speedup. Made ClusterNotification constants public.
+				Dropped some of the logging levels from INFO down to DEBUG. Release
+				has been split into two a binary release ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.0 beta 1" /> <a
+					href="/display/CACHE/OSCache+2.0+beta+1">OSCache 2.0 beta 1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (19th July 2003 by Chris Miller) New Features: CACHE11
+				http://jira.opensymphony.com/browse/CACHE11 Cache grouping support.
+				This allows cache entries to be placed into an arbitrary group or
+				groups and flushed with a single flushGroup() call. CACHE30 http ...
+				</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.0 beta 2" /> <a
+					href="/display/CACHE/OSCache+2.0+beta+2">OSCache 2.0 beta 2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (4th August 2003 by Chris Miller) New Features: Now supports
+				JavaGroups version 2.1. JMS Clustering support has been added
+				Romulus Pasca. Clustering code has been refactored. As a result of
+				this, some of the clustering ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.0.1" /> <a
+					href="/display/CACHE/OSCache+2.0.1">OSCache 2.0.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (4th November 2003 by Chris Miller) Improvements: CACHE56
+				http://jira.opensymphony.com/browse/CACHE56 Refresh period is no
+				longer mandatory. CACHE51
+				http://jira.opensymphony.com/browse/CACHE51 Added an <cache:addgroup />
+				tag. This allows cache groups to be dynamically added ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.0.2" /> <a
+					href="/display/CACHE/OSCache+2.0.2">OSCache 2.0.2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (22nd January 2004 by Mathias Bogaert) Improvements: Website
+				documentation updates. Added OSCache in the Wild. Bug Fixes: CACHE63
+				http://jira.opensymphony.com/browse/CACHE63 NullPointerException in
+				GeneralCacheAdministrator#destroy(). CACHE44
+				http://jira.opensymphony.com/browse/CACHE44 Multi threading issues
+				...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.1" /> <a
+					href="/display/CACHE/OSCache+2.1">OSCache 2.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (18th January 2005 by Andres March) New Features: Added
+				HashDiskPersistenceListner CACHE132
+				http://jira.opensymphony.com/browse/CACHE132 that hashes file names
+				in order to eliminate nasty characters and overly long names Added
+				property that allows cache entries ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.1.1" /> <a
+					href="/display/CACHE/OSCache+2.1.1">OSCache 2.1.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (1st May 2005 by Andres March) Improvements: The taglib URI
+				was changed to {{http://www.opensymphony.com/oscache}} in CACHE61
+				http://jira.opensymphony.com/browse/CACHE61 The
+				DiskPersistenceListener escapes '?' now and guarantees that the
+				filenames will be unique based on the cache key, see CACHE110 http
+				...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.2" /> <a
+					href="/display/CACHE/OSCache+2.2">OSCache 2.2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes Final (6th November 2005 by Lars Torunski) Additionally to the
+				2.2 RC OSCache 2.2 RC improvements, the final release was enhanced
+				by: Allow cache group definition in CacheFilter Option to specify
+				when to send ExpiresHeader Allow disabling ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.2 RC" /> <a
+					href="/display/CACHE/OSCache+2.2+RC">OSCache 2.2 RC</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes Release Candidate (18th September 2005 by Lars Torunski)
+				Besides bugs being fixed, major improvements have been made to the
+				CacheFilter in many ways: Default initialization of the last
+				modified header which reduces transaction overhead ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.3" /> <a
+					href="/display/CACHE/OSCache+2.3">OSCache 2.3</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (6th March 2006 by Lars Torunski) This release includes
+				additional improvements to the CacheFilter: CRON expressions to
+				expire content at specific dates and/or times Pluggable
+				EntryRefreshPolicy Reduced memory consumption Disk persistence:
+				Faster disk persistence Avoid ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.3.1" /> <a
+					href="/display/CACHE/OSCache+2.3.1">OSCache 2.3.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (19th June 2006 by Lars Torunski) This maintenance release of
+				2.3 has one enhancement: CacheFilter: Default initialization of the
+				CacheControl maxage Bug fixes: Cache.flushAll(Date flushDate) won't
+				throw NeedsRefreshException when flush date is not yet ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.3.2" /> <a
+					href="/display/CACHE/OSCache+2.3.2">OSCache 2.3.2</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (23rd July 2006 by Lars Torunski) This maintenance release of
+				2.3.1 has one enhancement: The removeEntry method in the Cache
+				removes the entry from its groups now Bug fixes: Method
+				addGroupMappings leads to inconsistent memory cache ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.4" /> <a
+					href="/display/CACHE/OSCache+2.4">OSCache 2.4</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (1st Mai 2007 by Lars Torunski) New features and enhancements
+				Furthermore the next major release 2.4 enhances the CacheFilter and
+				allows a better integration with the Spring Framework
+				http://www.springframework.org/ and JMX Monitoring. Setting
+				CacheFilter parameters runtime ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache 2.4.1" /> <a
+					href="/display/CACHE/OSCache+2.4.1">OSCache 2.4.1</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Release
+				Notes (1st July 2007 by Lars Torunski) This maintenance release of
+				2.4.1 has two bug fixes: The cacheFlushed method is not being
+				invoked on the CacheEntryEventListener CacheFilter maxage parameter
+				MAXAGENOINIT not set properly JIRA Issue List</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="OSCache in the Wild" /> <a
+					href="/display/CACHE/OSCache+in+the+Wild">OSCache in the Wild</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">following
+				are some of the sites that are using OSCache in production. This is
+				far from an exhaustive list of course! If you have or know of a site
+				using OSCache, please let us know so we can add it to the list.
+				While not required, any ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-P"></a>P</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-Q"></a>Q</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-R"></a>R</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Requirements" /> <a
+					href="/display/CACHE/Requirements">Requirements</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache can
+				be used directly to provide caching for any Java application. Using
+				the OSCache tag library JSP Tags requires Servlet 2.3 and JSP 1.2
+				support (included in J2EE 1.3) to run properly. There is no
+				dependency on a servlet container if the OSCache ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Roadmap" /> <a
+					href="/display/CACHE/Roadmap">Roadmap</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Scope This
+				page and the mailing list
+				https://oscache.dev.java.net/servlets/ProjectMailingListList are
+				provided for discussion purposes about the roadmap of OSCache and
+				discussing new features and improvements. See also the JIRA Road Map
+				http://jira.opensymphony.com/browse/CACHE?report=com.atlassian.jira.plugin.system.project:roadmappanel
+				...</div>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-S"></a>S</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Spring" /> <a
+					href="/display/CACHE/Spring">Spring</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Configuring
+				a {{GeneralCacheAdministrator}} A GeneralCacheAdministrator API
+				Usage instance that picks up configuration from an
+				oscache.properties Configuration file can be configured within
+				Spring using the following code: <bean id="cacheAdministrator"
+					class="com.opensymphony.oscache.general.GeneralCacheAdministrator"
+					destroymethod="destroy" /> Notice that a {{destorymethod ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="Statistics" /> <a
+					href="/display/CACHE/Statistics">Statistics</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">Description
+				With the cache event handlers
+				http://www.opensymphony.com/oscache/api/com/opensymphony/oscache/base/events/packagesummary.html
+				a listerner can be implemented to provide cache hits and misses
+				information. You can copy and paste the following code to get a
+				statistic ...</div>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="SVN and Compiling OSCache" /> <a
+					href="/display/CACHE/SVN+and+Compiling+OSCache">SVN and
+				Compiling OSCache</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">SVN The
+				OSCache SVN repository is hosted at
+				http://svn.opensymphony.com/svn/oscache. You can get the sources
+				anonymously by using e.g. Subclipse http://subclipse.tigris.org a
+				Subversion http://subversion.tigris.org Eclipse Plugin. If you want
+				to build OSCache from SVN ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-T"></a>T</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-U"></a>U</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-V"></a>V</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-W"></a>W</h4>
+				<img src="/images/icons/docs_16.gif" height="16" width="16"
+					border="0" align="absmiddle" title="What is OSCache" /> <a
+					href="/display/CACHE/What+is+OSCache">What is OSCache</a> <br />
+				<div class="smalltext" style="margin: 0 0 0 36px">OSCache is a
+				widely used, high performance J2EE caching framework. OSCache solves
+				fundamental problems for dynamic websites: # Caching Dynamic Content
+				Dynamic content of some form must often be executed during each
+				request, but sometimes that content doesn't ...</div>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-X"></a>X</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-Y"></a>Y</h4>
+				</td>
+			</tr>
+			<tr valign="top">
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-Z"></a>Z</h4>
+				</td>
+				<td>
+				<h4 style="margin-top: 0px"><a name="index-%21@%23%24"></a>!@#$</h4>
+				</td>
+			</tr>
+		</table>
+		</p>
+
+
+		</td>
+	</tr>
+</table>
+</body>
+</html>
diff --git a/docs/wiki/navpanel.jsp b/docs/wiki/navpanel.jsp
new file mode 100644
index 0000000..e952c9a
--- /dev/null
+++ b/docs/wiki/navpanel.jsp
@@ -0,0 +1 @@
+<%@ include file="../navpanel.jsp" %>
\ No newline at end of file
diff --git a/docs/wiki/styles/site.css b/docs/wiki/styles/site.css
new file mode 100644
index 0000000..68ff873
--- /dev/null
+++ b/docs/wiki/styles/site.css
@@ -0,0 +1,1511 @@
+body, p, td, table, tr, .bodytext, .stepfield {
+	font-family: Verdana, arial, sans-serif;
+	font-size: 11px;
+	line-height: 16px;
+	color: #000000;
+	font-weight: normal;
+}
+#PageContent {
+	text-align: left;
+	background-color: #fff;
+	padding: 0px;
+	margin: 0px;
+    padding-bottom:20px;
+}
+/*
+** when this stylesheet is used for the Tiny MCE Wysiwyg editor's edit area, we can't
+** use an id=PageContent or class=wiki-content, so we must
+** set the body style to that used for PageContent, and p to that used for wiki-content.
+*/
+
+body {
+	margin: 0px;
+	padding: 0px;
+	text-align: center;
+    background-color: #f0f0f0;
+}
+
+ at media print {
+
+body {
+    background-color: #fff;
+}
+
+}
+
+.monospaceInput {
+    font:12px monospace
+}
+
+.wiki-content p, .commentblock p {
+    margin: 16px 0px 16px 0px;
+    padding: 0px;
+}
+
+.wiki-content-preview {
+    padding: 5px;
+    border-left: 1px solid #3c78b5;
+    border-right: 1px solid #3c78b5;
+}
+
+ul, ol {
+    margin-top: 2px;
+    margin-bottom: 2px;
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+pre {
+    padding: 0px;
+    margin-top: 5px;
+    margin-left: 15px;
+    margin-bottom: 5px;
+    margin-right: 5px;
+    text-align: left;
+}
+
+.helpheading {
+    font-weight: bold;
+    background-color: #D0D9BD;
+        border-bottom: 1px solid #3c78b5;
+        padding: 4px 4px 4px 4px;
+        margin: 0px;
+        margin-top: 10px;
+}
+.helpcontent {
+        padding: 4px 4px 20px 4px;
+    background-color: #f5f7f1;
+}
+
+.code {
+ 	border: 1px dashed #3c78b5;
+    font-size: 11px;
+	font-family: Courier;
+    margin: 10px;
+	line-height: 13px;
+}
+
+.focusedComment {
+    background: #ffffce;
+}
+
+.commentBox, .focusedComment {
+    padding: 10px;
+    margin: 5px 0 5px 0;
+    border: 1px #bbb solid;
+}
+
+.codeHeader {
+    background-color: #f0f0f0;
+ 	border-bottom: 1px dashed #3c78b5;
+    padding: 3px;
+	text-align: center;
+}
+
+.codeContent {
+    text-align: left;
+    background-color: #f0f0f0;
+    padding: 3px;
+}
+
+.preformatted {
+ 	border: 1px dashed #3c78b5;
+    font-size: 11px;
+	font-family: Courier;
+    margin: 10px;
+	line-height: 13px;
+}
+
+.preformattedHeader {
+    background-color: #f0f0f0;
+ 	border-bottom: 1px dashed #3c78b5;
+    padding: 3px;
+	text-align: center;
+}
+
+.preformattedContent {
+    background-color: #f0f0f0;
+    padding: 3px;
+}
+
+.panel {
+ 	border: 1px dashed #3c78b5;
+    margin: 10px;
+    margin-top: 0px;
+}
+
+.panelHeader {
+    background-color: #f0f0f0;
+ 	border-bottom: 1px dashed #3c78b5;
+    padding: 3px;
+	text-align: center;
+}
+
+.panelContent {
+    background-color: #f0f0f0;
+    padding: 5px;
+}
+
+.anonymousAlert {
+    background-color: #f0f0f0;
+ 	border: 1px dashed red;
+    font-size: 11px;
+    padding: 10px 5px 10px 5px;
+    margin: 4px;
+	line-height: 13px;
+}
+
+.lockAlert {
+    background-color: #f0f0f0;
+    width: 50%;
+ 	border: 1px dashed red;
+    font-size: 11px;
+    padding: 10px 5px 10px 5px;
+    margin: 4px;
+	line-height: 13px;
+}
+
+
+.code-keyword {
+  color: #000091;
+  background-color: inherit;
+}
+
+.code-object {
+  color: #910091;
+  background-color: inherit;
+}
+
+.code-quote {
+  color: #009100;
+  background-color: inherit;
+}
+
+.code-comment {
+  color: #808080;
+  background-color: inherit;
+}
+
+
+.code-xml .code-keyword {
+  color: inherit;
+  font-weight: bold;
+}
+
+.code-tag {
+  color: #000091;
+  background-color: inherit;
+}
+
+.breadcrumbs {
+    background-color: #f0f0f0;
+ 	border-color: #3c78b5;
+	border-width: 1px 0px 1px 0px;
+	border-style: solid;
+    font-size: 11px;
+    padding: 3px 0px 3px 0px;
+}
+
+.navmenu {
+    border: 1px solid #ccc;
+}
+
+.menuheading {
+    font-weight: bold;
+    background-color: #f0f0f0;
+ 	border-bottom: 1px solid #3c78b5;
+	padding: 4px 4px 2px 4px;
+}
+
+.menuitems {
+	padding: 4px 4px 20px 4px;
+}
+
+.rightpanel {
+    border-left: 1px solid #ccc;
+    border-bottom: 1px solid #ccc;
+}
+
+#helpheading {
+    text-align: left;
+    font-weight: bold;
+    background-color: #D0D9BD;
+ 	border-bottom: 1px solid #3c78b5;
+	padding: 4px 4px 4px 4px;
+	margin: 0px;
+}
+#helpcontent {
+	padding: 4px 4px 4px 4px;
+    background-color: #f5f7f1;
+}
+.helptab-unselected {
+    font-weight: bold;
+	padding: 5px;
+    background-color: #f5f7f1;
+}
+.helptab-selected {
+    font-weight: bold;
+    background-color: #D0D9BD;
+	padding: 5px;
+}
+.helptabs {
+    margin: 0px;
+    background-color: #f5f7f1;
+	padding: 5px;
+}
+.infopanel-heading {
+    font-weight: bold;
+	padding: 4px 0px 2px 0px;
+}
+
+.pagebody {
+}
+
+.pageheader {
+	padding: 5px 5px 5px 0px;
+ 	border-bottom: 1px solid #3c78b5;
+}
+
+.pagetitle {
+	font-size: 22px;
+	font-weight: bold;
+	font-family: Arial, sans-serif;
+	color: #003366;
+}
+
+.newpagetitle {
+    color: #ccc !important;
+}
+
+.steptitle {
+	font-size: 18px;
+	font-weight: bold;
+	font-family: Arial, sans-serif;
+	color: #003366;
+	margin-bottom: 7px;
+}
+
+.substeptitle {
+    font-size: 12px;
+    font-weight: bold;
+    font-family: Arial, sans-serif;
+    color: #003366;
+    margin: 2px 4px 4px 4px;
+    padding: 2px 4px 1px 4px;
+}
+
+.stepdesc {
+    font-family: Verdana, arial, sans-serif;
+	font-size: 11px;
+	line-height: 16px;
+	font-weight: normal;
+    color: #666666;
+    margin-top: 7px;
+    margin-bottom: 7px;
+}
+
+.steplabel {
+    font-weight: bold;
+    margin-right: 4px;
+    color: black;
+    float: left;
+    width: 15%;
+    text-align: right;
+}
+
+.stepfield {
+    background: #f0f0f0;
+    padding: 5px;
+}
+
+.submitButtons{
+    margin-top:5px;
+    text-align:right;
+}
+
+.formtitle {
+	font-size: 12px;
+	font-weight: bold;
+	font-family: Arial, sans-serif;
+	color: #003366;
+}
+
+.sectionbottom {
+    border-bottom: 1px solid #3c78b5;
+}
+
+.topRow {
+    border-top: 2px solid #3c78b5;
+}
+
+.tabletitle {
+	font-size: 14px;
+	font-weight: bold;
+	font-family: Arial, sans-serif;
+    padding: 3px 0px 2px 0px;
+    margin: 8px 4px 2px 0px;
+	color: #003366;
+	border-bottom: 2px solid #3c78b5;
+}
+.pagesubheading {
+    color: #666666;
+    font-size: 10px;
+    padding: 0px 0px 5px 0px;
+}
+
+HR {
+	color: 3c78b5;
+	height: 1;
+}
+
+A:link, A:visited, A:active, A:hover {
+	color: #003366;
+}
+
+h1 A:link, h1 A:visited, h1 A:active {
+	text-decoration: none;
+}
+
+h1 A:hover {
+    border-bottom: 1px dotted #003366;
+}
+
+.wiki-content > :first-child, .commentblock > :first-child {
+    margin-top: 3px;
+}
+
+.logocell {
+    padding: 10px;
+}
+
+input {
+	font-family: verdana, geneva, arial, sans-serif;
+	font-size: 11px;
+	color: #000000;
+}
+
+textarea, textarea.editor {
+	font-family: verdana, geneva, arial, sans-serif;
+	font-size: 11px;
+	color: #333333;
+}
+
+/* use logoSpaceLink instead.
+.spacenametitle {
+	font: 21px/31px Impact, Arial, Helvetica;
+    font-weight: 100;
+    color: #999999;
+	margin: 0px;
+}
+.spacenametitle img {
+  margin: 0 0 -4px 0;
+}
+.spacenametitle a {
+    text-decoration: none;
+    color: #999999;
+}
+.spacenametitle a:visited {
+    text-decoration: none;
+    color: #999999;
+}*/
+
+.spacenametitle-printable {
+	font: 20px/25px Impact, Arial, Helvetica;
+    font-weight: 100;
+    color: #999999;
+	margin: 0px;
+}
+.spacenametitle-printable a {
+    text-decoration: none;
+    color: #999999;
+}
+.spacenametitle-printable a:visited {
+    text-decoration: none;
+    color: #999999;
+}
+
+.blogDate {
+	font-weight: bold;
+	text-decoration: none;
+	color: black;
+}
+
+.blogSurtitle {
+    background: #f0f0f0;
+ 	border: 1px solid #ddd;
+	padding: 3px;
+	margin: 1px 1px 10px 1px;
+}
+
+.blogHeading {
+    font-size: 20px;
+    line-height: normal;
+    font-weight: bold;
+    padding: 0px;
+    margin: 0px;
+}
+
+.blogHeading a {
+   text-decoration: none;
+   color: black;
+}
+
+.endsection {
+	align: right;
+	color: #666666;
+	margin-top: 10px;
+}
+.endsectionleftnav {
+	align: right;
+	color: #666666;
+	margin-top: 10px;
+}
+
+h1 {
+	font-size: 24px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+	color: #003366;
+ 	border-bottom: 1px solid #3c78b5;
+	padding: 2px;
+	margin: 36px 0px 4px 0px;
+}
+
+h2 {
+	font-size: 18px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+ 	border-bottom: 1px solid #3c78b5;
+	padding: 2px;
+	margin: 27px 0px 4px 0px;
+}
+
+h3 {
+	font-size: 14px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+	padding: 2px;
+	margin: 21px 0px 4px 0px;
+}
+
+h4 {
+	font-size: 12px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+	padding: 2px;
+	margin: 18px 0px 4px 0px;
+}
+
+h4.search {
+	font-size: 12px;
+	line-height: normal;
+	font-weight: normal;
+	background-color: #f0f0f0;
+	padding: 4px;
+	margin: 18px 0px 4px 0px;
+}
+
+h5 {
+	font-size: 10px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+	padding: 2px;
+	margin: 14px 0px 4px 0px;
+}
+
+h6 {
+	font-size: 8px;
+	line-height: normal;
+	font-weight: bold;
+	background-color: #f0f0f0;
+	padding: 2px;
+	margin: 14px 0px 4px 0px;
+}
+
+.smallfont {
+    font-size: 10px;
+}
+.descfont {
+    font-size: 10px;
+    color: #666666;
+}
+.smallerfont {
+    font-size: 9px;
+}
+.smalltext {
+    color: #666666;
+    font-size: 10px;
+}
+.smalltext a {
+    color: #666666;
+}
+.smalltext-blue {
+    color: #3c78b5;
+    font-size: 10px;
+}
+.surtitle {
+    margin-left: 1px;
+    margin-bottom: 5px;
+    font-size: 14px;
+    color: #666666;
+}
+
+/* css hack found here:  http://www.fo3nix.pwp.blueyonder.co.uk/tutorials/css/hacks/ */
+.navItemOver { font-size: 10px; font-weight: bold; color: #dedede; background-color: #669900; cursor: hand; voice-family: '\'}\''; voice-family:inherit; cursor: pointer;}
+.navItemOver a { color: #dedede; background-color:#669900; text-decoration: none; }
+.navItemOver a:visited { color: #dedede; background-color:#669900; text-decoration: none; }
+.navItemOver a:hover { color: #dedede; background-color:#669900; text-decoration: none; }
+.navItem { font-size: 10px; font-weight: bold; color: #ffffff; background-color: #99CCFF; }
+.navItem a { color: #666666; text-decoration: none; }
+.navItem a:hover { color: #666666; text-decoration: none; }
+.navItem a:visited { color: #666666; text-decoration: none; }
+
+div.padded { padding: 4px; }
+div.thickPadded { padding: 10px; }
+h3.macrolibrariestitle {
+    margin: 0px 0px 0px 0px;
+}
+
+div.centered { text-align: center; margin: 10px; }
+div.centered table {margin: 0px auto; text-align: left; }
+
+.tableview table {
+    margin: 0;
+}
+
+.tableview th {
+    text-align: left;
+    color: #003366;
+    font-size: 12px;
+    padding: 5px 0px 0px 5px;
+    border-bottom: 2px solid #3c78b5;
+}
+.tableview td {
+    text-align: left;
+    border-color: #ccc;
+    border-width: 0px 0px 1px 0px;
+    border-style: solid;
+    margin: 0;
+    padding: 4px 10px 4px 5px;
+}
+
+.grid {
+    margin: 2px 0px 5px 0px;
+    border-collapse: collapse;
+}
+.grid th  {
+    border: 1px solid #ccc;
+    padding: 2px 4px 2px 4px;
+    background: #f0f0f0;
+    text-align: center;
+}
+.grid td  {
+    border: 1px solid #ccc;
+    padding: 3px 4px 3px 4px;
+}
+.gridHover {
+	background-color: #f9f9f9;
+}
+
+td.infocell {
+    background-color: #f0f0f0;
+}
+.label {
+	font-weight: bold;
+	color: #003366;
+}
+
+label {
+	font-weight: bold;
+	color: #003366;
+}
+
+.error {
+	background-color: #fcc;
+}
+
+.errorBox {
+	background-color: #fcc;
+    border: 1px solid #c00;
+    padding: 5px;
+    margin: 5px;
+}
+
+.errorMessage {
+	color: #c00;
+}
+
+.success {
+	background-color: #dfd;
+}
+
+.successBox {
+	background-color: #dfd;
+    border: 1px solid #090;
+    padding: 5px;
+    margin-top:5px;
+    margin-bottom:5px;
+}
+
+blockquote {
+	padding-left: 10px;
+	padding-right: 10px;
+	margin-left: 5px;
+	margin-right: 0px;
+	border-left: 1px solid #3c78b5;
+}
+
+table.confluenceTable
+{
+    margin: 5px;
+    border-collapse: collapse;
+}
+
+/* Added as a temporary fix for CONF-4223. The table elements appear to be inheriting the border: none attribute from the sectionMacro class */
+table.confluenceTable td.confluenceTd
+{
+    border-width: 1px;
+    border-style: solid;
+    border-color: #ccc;
+    padding: 3px 4px 3px 4px;
+}
+
+/* Added as a temporary fix for CONF-4223. The table elements appear to be inheriting the border: none attribute from the sectionMacro class */
+table.confluenceTable th.confluenceTh
+{
+    border-width: 1px;
+    border-style: solid;
+    border-color: #ccc;
+    padding: 3px 4px 3px 4px;
+    background-color: #f0f0f0;
+    text-align: center;
+}
+
+td.confluenceTd
+{
+    border-width: 1px;
+    border-style: solid;
+    border-color: #ccc;
+    padding: 3px 4px 3px 4px;
+}
+
+th.confluenceTh
+{
+    border-width: 1px;
+    border-style: solid;
+    border-color: #ccc;
+    padding: 3px 4px 3px 4px;
+    background-color: #f0f0f0;
+    text-align: center;
+}
+
+DIV.small {
+	font-size: 9px;
+}
+
+H1.pagename {
+	margin-top: 0px;
+}
+
+IMG.inline  {}
+
+.loginform {
+    margin: 5px;
+    border: 1px solid #ccc;
+}
+
+/* The text how the "This is a preview" comment should be shown. */
+.previewnote { text-align: center;
+                font-size: 11px;
+                    color: red; }
+
+/* How the preview content should be shown */
+.previewcontent { background: #E0E0E0; }
+
+/* How the system messages should be shown (DisplayMessage.jsp) */
+.messagecontent { background: #E0E0E0; }
+
+/* How the "This page has been modified..." -comment should be shown. */
+.conflictnote { }
+
+.createlink {
+    color: maroon;
+}
+a.createlink {
+    color: maroon;
+}
+.templateparameter {
+    font-size: 9px;
+    color: darkblue;
+}
+
+.diffadded {
+    background: #ddffdd;
+    padding: 1px 1px 1px 4px;
+	border-left: 4px solid darkgreen;
+}
+.diffdeleted {
+    color: #999;
+    background: #ffdddd;
+    padding: 1px 1px 1px 4px;
+	border-left: 4px solid darkred;
+}
+.diffnochange {
+    padding: 1px 1px 1px 4px;
+	border-left: 4px solid lightgrey;
+}
+.differror {
+    background: brown;
+}
+.diff {
+    font-family: lucida console, courier new, fixed-width;
+	font-size: 12px;
+	line-height: 14px;
+}
+.diffaddedchars {
+    background-color:#99ff99;
+    font-weight:bolder;
+}
+.diffremovedchars {
+    background-color:#ff9999;
+    text-decoration: line-through;
+    font-weight:bolder;
+}
+
+.greybackground {
+    background: #f0f0f0
+}
+
+.greybox {
+ 	border: 1px solid #ddd;
+	padding: 3px;
+	margin: 1px 1px 10px 1px;
+}
+
+.borderedGreyBox {
+    border: 1px solid #cccccc;
+    background-color: #f0f0f0;
+    padding: 10px;
+}
+
+.greyboxfilled {
+ 	border: 1px solid #ddd;
+    background: #f0f0f0;
+    padding: 3px;
+	margin: 1px 1px 10px 1px;
+}
+
+.navBackgroundBox {
+    padding: 5px 5px 5px 5px;
+    font-size: 22px;
+	font-weight: bold;
+	font-family: Arial, sans-serif;
+	color: white;
+    background: #99CCFF;
+    text-decoration: none;
+}
+
+.previewBoxTop {
+	background-color: #f0f0f0;
+    border-width: 1px 1px 0px 1px;
+    border-style: solid;
+    border-color: #3c78b5;
+    padding: 5px;
+    margin: 5px 0px 0px 0px;
+    text-align: center;
+}
+.previewContent {
+    background-color: #fff;
+ 	border-color: #3c78b5;
+	border-width: 0px 1px 0px 1px;
+	border-style: solid;
+	padding: 10px;
+	margin: 0px;
+}
+.previewBoxBottom {
+	background-color: #f0f0f0;
+    border-width: 0px 1px 1px 1px;
+    border-style: solid;
+    border-color: #3c78b5;
+    padding: 5px;
+    margin: 0px 0px 5px 0px;
+    text-align: center;
+}
+
+.functionbox {
+    background-color: #f0f0f0;
+ 	border: 1px solid #3c78b5;
+	padding: 3px;
+	margin: 1px 1px 10px 1px;
+}
+
+.functionbox-greyborder {
+    background-color: #f0f0f0;
+ 	border: 1px solid #ddd;
+	padding: 3px;
+	margin: 1px 1px 10px 1px;
+}
+
+.search-highlight {
+    background-color: #ffffcc;
+}
+
+/* normal (white) background */
+.rowNormal {
+    background-color: #ffffff;
+ }
+
+/* alternate (pale yellow) background */
+.rowAlternate {
+    background-color: #f7f7f7;
+}
+
+/* used in the list attachments table */
+.rowAlternateNoBottomColor {
+    background-color: #f7f7f7;
+}
+
+.rowAlternateNoBottomNoColor {
+}
+
+.rowAlternateNoBottomColor td {
+    border-bottom: 0px;
+}
+
+.rowAlternateNoBottomNoColor td {
+    border-bottom: 0px;
+}
+
+/* row highlight (grey) background */
+.rowHighlight {
+    background-color: #f0f0f0;
+
+}
+
+TD.greenbar {FONT-SIZE: 2px; BACKGROUND: #00df00; BORDER: 1px solid #9c9c9c; PADDING: 0px; }
+TD.redbar {FONT-SIZE: 2px; BACKGROUND: #df0000; BORDER: 1px solid #9c9c9c; PADDING: 0px; }
+TD.darkredbar {FONT-SIZE: 2px; BACKGROUND: #af0000; BORDER: 1px solid #9c9c9c; PADDING: 0px; }
+
+TR.testpassed {FONT-SIZE: 2px; BACKGROUND: #ddffdd; PADDING: 0px; }
+TR.testfailed {FONT-SIZE: 2px; BACKGROUND: #ffdddd; PADDING: 0px; }
+
+.toolbar  {
+    margin: 0px;
+    border-collapse: collapse;
+}
+
+.toolbar td  {
+    border: 1px solid #ccc;
+    padding: 2px 2px 2px 2px;
+    color: #ccc;
+}
+
+td.noformatting {
+    border-width: 0px;
+    border-style: none;
+    text-align: center;
+	padding: 0px;
+}
+
+.commentblock {
+    margin: 12px 0 12px 0;
+}
+
+/*
+ * Divs displaying the license information, if necessary.
+ */
+.license-eval, .license-none, .license-nonprofit {
+    border-top: 1px solid #bbbbbb;
+    text-align: center;
+    font-size: 10px;
+    font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+.license-eval, .license-none {
+    background-color: #ffcccc;
+}
+
+.license-eval b, .license-none b {
+    color: #990000
+}
+
+.license-nonprofit {
+    background-color: #ffffff;
+}
+
+/*
+ * The shadow at the bottom of the page between the main content and the
+ * "powered by" section.
+ */
+.bottomshadow {
+    height: 12px;
+    background-image: url("$req.contextPath/images/border/border_bottom.gif");
+    background-repeat: repeat-x;
+}
+
+/*
+ * Styling of the operations box
+ */
+.navmenu .operations li, .navmenu .operations ul {
+    list-style: none;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.navmenu .operations ul {
+    margin-bottom: 9px;
+}
+
+.navmenu .label {
+    font-weight: inherit;
+}
+
+/*
+ * Styling of ops as a toolbar
+ */
+.toolbar div {
+    display: none;
+}
+
+.toolbar .label {
+    display: none;
+}
+
+.toolbar .operations {
+    display: block;
+}
+
+.toolbar .operations ul {
+    display: inline;
+    list-style: none;
+    margin-left: 10px;
+    padding-left: 0;
+}
+
+.toolbar .operations li {
+    list-style: none;
+    display: inline;
+}
+
+/* list page navigational tabs */
+#foldertab {
+padding: 3px 0px 3px 8px;
+margin-left: 0;
+border-bottom: 1px solid #99CCFF;
+font: bold 11px Verdana, sans-serif;
+}
+
+#foldertab li {
+list-style: none;
+margin: 0;
+display: inline;
+}
+
+#foldertab li a {
+padding: 3px 0.5em;
+margin-left: 3px;
+border: 1px solid #99CCFF;
+border-bottom: none;
+background: #99CCFF;
+text-decoration: none;
+}
+
+#foldertab li a:link { color: #666666; }
+#foldertab li a:visited { color: #666666; }
+
+#foldertab li a:hover {
+color: #dedede;
+background: #669900;
+border-color: #669900;
+}
+
+#foldertab li a.current {
+background: white;
+border-bottom: 1px solid white;
+color: black;
+}
+
+#foldertab li a.current:link { color: black; }
+#foldertab li a.current:visited { color: black; }
+#foldertab li a.current:hover {
+background: white;
+border-bottom: 1px solid white;
+color: black;
+}
+
+/* alphabet list */
+ul#squaretab {
+margin-left: 0;
+padding-left: 0;
+white-space: nowrap;
+font: bold 8px Verdana, sans-serif;
+}
+
+#squaretab li {
+display: inline;
+list-style-type: none;
+}
+
+#squaretab a {
+padding: 2px 6px;
+border: 1px solid #99CCFF;
+}
+
+#squaretab a:link, #squaretab a:visited {
+color: #fff;
+background-color: #99CCFF;
+text-decoration: none;
+}
+
+#squaretab a:hover {
+color: #dedede;
+background-color: #669900;
+border-color: #669900;
+text-decoration: none;
+}
+
+#squaretab li a#current {
+background: white;
+color: black;
+}
+
+.blogcalendar * {
+    font-family:verdana, arial, sans-serif;
+    font-size:x-small;
+    font-weight:normal;
+    line-height:140%;
+    padding:2px;
+}
+
+
+table.blogcalendar {
+    border: 1px solid #3c78b5;
+}
+
+.blogcalendar th.calendarhead, a.calendarhead {
+    font-size:x-small;
+    font-weight:bold;
+    padding:2px;
+    text-transform:uppercase;
+    background-color: #99CCFF;
+    color: #ffffff;
+    letter-spacing: .3em;
+    text-transform: uppercase;
+}
+
+.calendarhead:visited {color: white;}
+.calendarhead:active {color: white;}
+.calendarhead:hover {color: white;}
+
+.blogcalendar th {
+    font-size:x-small;
+    font-weight:bold;
+    padding:2px;
+    background-color:#f0f0f0;
+}
+
+.blogcalendar td {
+    font-size:x-small;
+    font-weight:normal;
+}
+
+.searchGroup { padding: 0 0 10px 0; background: #f0f0f0; }
+.searchGroupHeading { font-size: 10px; font-weight: bold; color: #ffffff; background-color: #99CCFF; padding: 2px 4px 1px 4px; }
+.searchItem { padding: 1px 4px 1px 4px; }
+.searchItemSelected { padding: 1px 4px 1px 4px; font-weight: bold; background: #ddd; }
+
+/* permissions page styles */
+.permissionHeading {
+    border-bottom: #bbb; border-width: 0 0 1px 0; border-style: solid; font-size: 16px; text-align: left;
+}
+.permissionTab {
+    border-width: 0 0 0 1px; border-style: solid; background: #99CCFF; color: #666666; font-size: 10px;
+}
+.permissionSuperTab {
+    border-width: 0 0 0 1px; border-style: solid; background: #669900; color: #dedede;
+}
+.permissionCell {
+    border-left: #bbb; border-width: 0 0 0 1px; border-style: solid;
+}
+
+/* warning panel */
+.warningPanel { background: #FFFFCE; border:#F0C000 1px solid; padding: 8px; margin: 10px; }
+/* alert panel */
+.alertPanel { background: #FFCCCC; border:#C00 1px solid; padding: 8px; margin: 10px; }
+/* info panel */
+.infoPanel { background: #D8E4F1; border:#3c78b5 1px solid; padding: 8px; margin: 10px; }
+
+/* side menu highlighting (e.g. space content screen) */
+.optionPadded { padding: 2px; }
+.optionSelected { background-color: #ffffcc; padding: 2px; border: 1px solid #ddd; margin: -1px; }
+.optionSelected a { font-weight: bold; text-decoration: none; color: black; }
+
+/* information macros */
+.noteMacro { border-style: solid; border-width: 1px; border-color: #F0C000; background-color: #FFFFCE; text-align:left; margin-top: 5px; margin-bottom: 5px}
+.warningMacro { border-style: solid; border-width: 1px; border-color: #c00; background-color: #fcc; text-align:left; margin-top: 5px; margin-bottom: 5px}
+.infoMacro { border-style: solid; border-width: 1px; border-color: #3c78b5; background-color: #D8E4F1; text-align:left; margin-top: 5px; margin-bottom: 5px}
+.tipMacro { border-style: solid; border-width: 1px; border-color: #090; background-color: #dfd; text-align:left; margin-top: 5px; margin-bottom: 5px}
+.informationMacroPadding { padding: 5px 0 0 5px; }
+
+table.infoMacro td, table.warningMacro td, table.tipMacro td, table.noteMacro td, table.sectionMacro td {
+    border: none;
+}
+
+table.sectionMacroWithBorder td.columnMacro { border-style: dashed; border-width: 1px; border-color: #cccccc;}
+
+.pagecontent
+{
+    padding: 10px;
+    text-align: left;
+}
+
+/* styles for links in the top bar */
+.topBarDiv a:link {color: #ffffff;}
+.topBarDiv a:visited {color: #ffffff;}
+.topBarDiv a:active {color: #ffffff;}
+.topBarDiv a:hover {color: #ffffff;}
+.topBarDiv {color: #ffffff;}
+
+.topBar {
+    background-color: #669900;
+}
+
+
+/* styles for extended operations */
+.greyLinks a:link {color: #666666; text-decoration:underline;}
+.greyLinks a:visited {color: #666666; text-decoration:underline;}
+.greyLinks a:active {color: #666666; text-decoration:underline;}
+.greyLinks a:hover {color: #666666; text-decoration:underline;}
+.greyLinks {color: #666666; display:block; padding: 10px}
+
+.logoSpaceLink {color: #999999; text-decoration: none}
+.logoSpaceLink a:link {color: #999999; text-decoration: none}
+.logoSpaceLink a:visited {color: #999999; text-decoration: none}
+.logoSpaceLink a:active {color: #999999; text-decoration: none}
+.logoSpaceLink a:hover {color: #003366; text-decoration: none}
+
+/* basic panel (basicpanel.vmd) style */
+.basicPanelContainer {border: 1px solid #99CCFF; margin-top: 2px; margin-bottom: 8px; width: 100%}
+.basicPanelTitle {padding: 5px; margin: 0px; background-color: #f0f0f0; color: black; font-weight: bold;}
+.basicPanelBody {padding: 5px; margin: 0px}
+
+.separatorLinks a:link {color: white}
+.separatorLinks a:visited {color: white}
+.separatorLinks a:active {color: white}
+
+.greynavbar {background-color: #f0f0f0; border-top: 1px solid #99CCFF; margin-top: 2px}
+
+div.headerField {
+    float: left;
+    width: auto;
+    height: 100%;
+}
+
+.headerFloat {
+    margin-left: auto;
+    width: 50%;
+}
+
+.headerFloatLeft {
+    float: left;
+    margin-right: 20px;
+    margin-bottom: 10px;
+}
+
+#headerRow {
+    padding: 10px;
+}
+
+div.license-personal {
+   background-color: #669900;
+   color: #666666;
+}
+
+div.license-personal a {
+   color: #666666;
+}
+
+.greyFormBox {
+    border: 1px solid #cccccc;
+    padding: 5px;
+}
+
+/* IE automatically adds a margin before and after form tags. Use this style to remove that */
+.marginlessForm {
+    margin: 0px;
+}
+
+.openPageHighlight {
+    background-color: #ffffcc;
+    padding: 2px;
+    border: 1px solid #ddd;
+}
+
+.editPageInsertLinks, .editPageInsertLinks a
+{
+    color: #666666;
+    font-weight: bold;
+    font-size: 10px;
+}
+
+/* Style for label heatmap. */
+.top10 a {
+    font-weight: bold;
+    font-size: 2em;
+    color: #003366;
+}
+.top25 a {
+    font-weight: bold;
+    font-size: 1.6em;
+    color: #003366;
+}
+.top50 a {
+    font-size: 1.4em;
+    color: #003366;
+}
+.top100 a {
+    font-size: 1.2em;
+    color: #003366;
+}
+
+.heatmap {
+    list-style:none;
+    width: 95%;
+    margin: 0px auto;
+}
+
+.heatmap a {
+    text-decoration:none;
+}
+
+.heatmap a:hover {
+    text-decoration:underline;
+}
+
+.heatmap li {
+    display: inline;
+}
+
+.minitab {
+padding: 3px 0px 3px 8px;
+margin-left: 0;
+margin-top: 1px;
+margin-bottom: 0px;
+border-bottom: 1px solid #99CCFF;
+font: bold 9px Verdana, sans-serif;
+text-decoration: none;
+float:none;
+}
+.selectedminitab {
+padding: 3px 0.5em;
+margin-left: 3px;
+margin-top: 1px;
+border: 1px solid #99CCFF;
+background: white;
+border-bottom: 1px solid white;
+color: #000000;
+text-decoration: none;
+}
+.unselectedminitab {
+padding: 3px 0.5em;
+margin-left: 3px;
+margin-top: 1px;
+border: 1px solid #99CCFF;
+border-bottom: none;
+background: #99CCFF;
+color: #ffffff;
+text-decoration: none;
+}
+
+a.unselectedminitab:hover {
+color: #dedede;
+background: #669900;
+border-color: #669900;
+}
+
+a.unselectedminitab:link { color: white; }
+a.unselectedminitab:visited { color: white; }
+
+a.selectedminitab:link { color: black; }
+a.selectedminitab:visited { color: black; }
+
+.linkerror { background-color: #fcc;}
+
+a.labelOperationLink:link {text-decoration: underline}
+a.labelOperationLink:active {text-decoration: underline}
+a.labelOperationLink:visited {text-decoration: underline}
+a.labelOperationLink:hover {text-decoration: underline}
+
+a.newLabel:link {background-color: #ddffdd}
+a.newLabel:active {background-color: #ddffdd}
+a.newLabel:visited {background-color: #ddffdd}
+a.newLabel:hover {background-color: #ddffdd}
+
+ul.square {list-style-type: square}
+
+.inline-control-link {
+    background: #ffc;
+    font-size: 9px;
+    color: #666;
+    padding: 2px;
+    text-transform: uppercase;
+    text-decoration: none;
+}
+
+
+.inline-control-link a:link {text-decoration: none}
+.inline-control-link a:active {text-decoration: none}
+.inline-control-link a:visited {text-decoration: none}
+.inline-control-link a:hover {text-decoration: none}
+
+.inline-control-link {
+    background: #ffc;
+    font-size: 9px;
+    color: #666;
+    padding: 2px;
+    text-transform: uppercase;
+    text-decoration: none;
+    cursor: pointer;
+}
+
+div.auto_complete {
+    width: 350px;
+    background: #fff;
+}
+div.auto_complete ul {
+    border: 1px solid #888;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+    list-style-type: none;
+}
+div.auto_complete ul li {
+    margin: 0;
+    padding: 3px;
+}
+div.auto_complete ul li.selected {
+    background-color: #ffb;
+}
+div.auto_complete ul strong.highlight {
+    color: #800;
+    margin: 0;
+    padding: 0;
+}
+
+/******* Edit Page Styles *******/
+.toogleFormDiv{
+    border:1px solid #A7A6AA;
+    background-color:white;
+    padding:5px;
+    margin-top: 5px;
+}
+
+.toogleInfoDiv{
+    border:1px solid #A7A6AA;
+    background-color:white;
+    display:none;
+    padding:5px;
+    margin-top: 10px;
+}
+
+.inputSection{
+    margin-bottom:20px;
+}
+
+#editBox{
+   border:1px solid lightgray;
+   background-color:#F0F0F0;
+}
+
+/******* Left Navigation Theme Styles ********/
+.leftnav li a {
+    text-decoration:none;
+    color:white;
+    margin:0px;
+    display:block;
+    padding:2px;
+    padding-left:5px;
+    background-color: #99CCFF;
+    border-top:1px solid #99CCFF;
+}
+
+.leftnav li a:active {color:white;}
+.leftnav li a:visited {color:white;}
+.leftnav li a:hover {background-color: #669900; color:white;}
+
+/* Added by Shaun during i18n */
+.replaced
+{
+    background-color: #33CC66;
+}
+
+.topPadding
+{
+    margin-top: 20px;
+}
+
+/* new form style */
+.form-block {
+    padding: 6px;
+}
+.form-error-block {
+    padding: 6px;
+    background: #fcc;
+    border-top: #f0f0f0 1px solid;
+    border-bottom: #f0f0f0 1px solid;
+    margin-bottom: 6px;
+    padding: 0 12px 0 12px;
+}
+.form-element-large {
+    font-size: 16px;
+    font-weight: bold;
+    font-family: Arial, sans-serif;
+    color: #003366;
+}
+
+.form-element-small {
+    font-size: 12px;
+    font-weight: bold;
+    font-family: Arial, sans-serif;
+    color: #003366;
+}
+
+.form-header {
+    background: lightyellow;
+    border-top: #f0f0f0 1px solid;
+    border-bottom: #f0f0f0 1px solid;
+    margin-bottom: 6px;
+    padding: 0 12px 0 12px;
+}
+.form-header p, .form-block p, .form-error-block p {
+    line-height: normal;
+    margin: 12px 0 12px 0;
+}
+.form-example {
+    color: #888;
+    font-size: 11px;
+}
+.form-divider {
+    border-bottom: #ccc 1px solid;
+    margin-bottom: 6px;
+}
+.form-buttons {
+    margin-top: 6px;
+    border-top: #ccc 1px solid;
+    border-bottom: #ccc 1px solid;
+    background: #f0f0f0;
+    padding: 10px;
+    text-align: center;
+}
+.form-buttons input {
+    width: 100px;
+}
+.form-block .error {
+    padding: 6px;
+    margin-bottom: 6px;
+}
\ No newline at end of file
diff --git a/ivy.xml b/ivy.xml
new file mode 100644
index 0000000..0a7b232
--- /dev/null
+++ b/ivy.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml-stylesheet type="text/xsl" href="http://www.jayasoft.fr/org/ivyrep/ivy-doc.xsl"?>
+<ivy-module version="1.0">
+    <info organisation="opensymphony" module="oscache"
+          revision="2.4.1"
+          status="release"
+          publication="20070707120000">
+        <license name="Apache" url="http://www.apache.org/licenses/LICENSE-2.0.txt"/>
+        <ivyauthor name="opensymphony" url="http://www.opensymphony.com/"/>
+
+        <description homepage="http://www.opensymphony.com/oscache/">
+			OSCache is a caching solution that includes a JSP tag library and set of classes to perform 
+			fine grained dynamic caching of JSP content, servlet responses or arbitrary objects. It 
+			provides both in memory and persistent on disk caches, and can allow your site to have 
+			graceful error tolerance (eg if an error occurs like your db goes down, you can serve the 
+			cached content so people can still surf the site almost without knowing).
+        </description>
+    </info>
+    
+    <configurations>
+        <conf name="build" visibility="private"/>
+        <conf name="default" description="Default configuration"/>
+        <conf name="jgroups" description="JGroups integration"/>
+    </configurations>
+
+    <publications>
+        <artifact name="oscache" type="jar" conf="default"/>
+    </publications>
+    
+    <dependencies>
+        <!-- build dependencies -->
+        <dependency org="clover" name="clover" rev="1.3.9" conf="build->*"/>
+        <dependency org="junit" name="junit" rev="3.8.2" conf="build->*"/>
+        <dependency org="javax.servlet" name="servletapi" rev="2.3" conf="build->*"/>
+        <dependency org="javax.jms" name="jms" rev="1.1" conf="build->*"/>
+        <dependency org="httpunit" name="httpunit" rev="1.6" conf="build->*"/>
+        <dependency org="com/clarkware" name="junitperf" rev="1.9.1" conf="build->*"/>
+        <dependency org="org.hibernate" name="hibernate3" rev="3.2.3" conf="build->*" />
+        <dependency org="net/sourceforge/groboutils" name="groboutils-core" rev="5" conf="build->*" />
+        <!--
+        <dependency org="backport-util-concurrent" name="backport-util-concurrent" rev="3.0" conf="build->*" />
+         -->
+
+        <!-- oscache -->
+        <dependency org="apache" name="commons-logging" rev="1.1" conf="default->*"/>
+
+        <!-- jgroups -->
+        <dependency org="jgroups" name="jgroups-all" rev="2.2.8" conf="jgroups->*"/>
+        
+    </dependencies>
+</ivy-module>
diff --git a/ivyconf.properties b/ivyconf.properties
new file mode 100644
index 0000000..c66d67a
--- /dev/null
+++ b/ivyconf.properties
@@ -0,0 +1,2 @@
+ivy.retrieve.pattern = lib/[artifact].[ext]
+integration.repository = lib/integration
diff --git a/ivyconf.xml b/ivyconf.xml
new file mode 100644
index 0000000..6e1e17a
--- /dev/null
+++ b/ivyconf.xml
@@ -0,0 +1,36 @@
+<ivyconf>
+    <properties file="ivyconf.properties"/>
+    <conf defaultResolver="default" checkUpToDate="true"/>
+    <resolvers>
+        <chain name="default">
+            <filesystem name="integration" checkmodified="true">
+                <ivy pattern="${integration.repository}/[organisation]/[module]/ivy-[revision].xml"/>
+                <artifact pattern="${integration.repository}/[organisation]/[module]/[artifact]-[revision].[ext]"/>
+            </filesystem>
+            <url name="opensymphony" checkmodified="true">
+                <ivy pattern="http://ivyrep.opensymphony.com/[organisation]/[module]/ivy-[revision].xml"/>
+                <artifact pattern="http://ivyrep.opensymphony.com/[organisation]/[module]/[artifact]-[revision].[type]"/>
+            </url>
+            <url name="maven2.opensymphony">
+                <artifact pattern="http://maven2.opensymphony.com/[organisation]/[module]/[revision]/[module]-[revision].[type]"/>
+            </url>
+            <url name="contegix">
+                <ivy pattern="http://repository.contegix.com/ivyrep/[organisation]/[module]/ivy-[revision].xml"/>
+                <artifact pattern="http://repository.contegix.com/ivyrep/[organisation]/[module]/[artifact]-[revision].[type]"/>
+            </url>
+            <ivyrep name="ivyrep"/>
+            <ibiblio name="contegix-maven" root="http://repository.contegix.com/maven"/>
+            <url name="maven">
+                <artifact pattern="http://www.ibiblio.org/maven/[organisation]/jars/[module]-[revision].[type]"/>
+            </url>
+            <url name="maven">
+                <artifact pattern="http://www.ibiblio.org/maven/[organisation]/jars/[module]-[revision].[type]"/>
+            </url>
+            <!--
+            <url name="maven2">
+                <artifact pattern="http://mirrors.ibiblio.org/pub/mirrors/maven2/[organisation]/[module]/[module]-[revision].[type]"/>
+            </url>
+            -->
+        </chain>
+    </resolvers>
+</ivyconf>
\ No newline at end of file
diff --git a/lib/.cvsignore b/lib/.cvsignore
new file mode 100644
index 0000000..f23b948
--- /dev/null
+++ b/lib/.cvsignore
@@ -0,0 +1 @@
+*.jar
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1ad59e9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,312 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>opensymphony</groupId>
+    <artifactId>oscache</artifactId>
+    <version>2.4.1</version>
+    <name>OSCache</name>
+    <description>
+        OSCache is a caching solution that includes a JSP tag library
+        and set of classes to perform fine grained dynamic caching of
+        JSP content, servlet responses or arbitrary objects.
+    </description>
+    <url>http://www.opensymphony.com/oscache/</url>
+    <organization>
+        <name>OpenSymphony</name>
+        <url>http://www.opensymphony.com/</url>
+    </organization>
+    <issueManagement>
+        <system>JIRA</system>
+        <url>http://jira.opensymphony.com/browse/CACHE</url>
+    </issueManagement>
+    <distributionManagement>
+        <!--
+          For this to work, you need to:
+
+          1) Get the private key from someone that trusts you :)
+          2) Add the following to ~/.m2/settings.xml
+
+          <settings>
+              ...
+              <servers>
+                  <server>
+                        <id>opensymphony</id>
+                        <username>maven</username>
+                        <privateKey>/path/to/private/maven_opensymphony_key</privateKey>
+                  </server>
+              </servers>
+              ...
+          </settings>
+          -->
+        <repository>
+            <id>opensymphony</id>
+            <name>OpenSymphony Maven Repository</name>
+            <url>scp://maven2.opensymphony.com/opt/repository/maven2</url>
+        </repository>
+    </distributionManagement>
+    <mailingLists>
+        <mailingList>
+            <name>User discussion and support list for OSCache</name>
+            <post>mailto:users at oscache.dev.java.net</post>
+            <subscribe>https://oscache.dev.java.net/servlets/ProjectMailingListList</subscribe>
+            <archive>https://oscache.dev.java.net/servlets/SummarizeList?listName=users</archive>
+            <otherArchives>
+                <otherArchive>http://forums.opensymphony.com/forum.jspa?forumID=4</otherArchive>
+            </otherArchives>
+        </mailingList>
+    </mailingLists>
+    <scm>
+        <url>http://svn.opensymphony.com/svn/oscache/trunk/</url>
+    </scm>
+    <licenses>
+        <license>
+            <name>The OpenSymphony Software License 1.1</name>
+            <url>http://opensymphony.com/webwork/license.action</url>
+            <comments>
+                This license is derived and fully compatible with the Apache Software
+                License - see http://www.apache.org/LICENSE.txt
+            </comments>
+        </license>
+    </licenses>
+    <build>
+        <sourceDirectory>src/java</sourceDirectory>
+        <testSourceDirectory>src/test</testSourceDirectory>
+        <resources>
+            <resource>
+                <directory>src/java</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+                <excludes>
+                    <exclude>**/*.java</exclude>
+                    <exclude>**/*.html</exclude>
+                </excludes>
+            </resource>
+            <resource>
+                <directory>src/etc</directory>
+                <includes>
+                    <include>**/*.tld</include>
+                </includes>
+                <excludes>
+                    <exclude>**/*.xml</exclude>
+                    <exclude>**/*.properties</exclude>
+                </excludes>
+            </resource>
+        </resources>
+        <testResources>
+            <testResource>
+                <directory>src/test</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+                <excludes>
+                    <exclude>**/*.java</exclude>
+                    <exclude>**/*.html</exclude>
+                </excludes>
+            </testResource>
+        </testResources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-idea-plugin</artifactId>
+                <configuration>
+                    <jdkName>1.5</jdkName>
+                    <exclude>target,test-output,.clover,build</exclude>
+                    <useShortDependencyNames>true</useShortDependencyNames>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-site-plugin</artifactId>
+                <version>2.0-beta-5</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <verbose>true</verbose>
+                    <source>1.4</source>
+                    <target>1.4</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.3</version>
+                <configuration>
+                    <includes>
+                        <include>**/*Test.java</include>
+                    </includes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-source</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <groups>
+                        <group>
+                            <title>OSCache Packages</title>
+                            <packages>com.opensymphony.oscache*</packages>
+                        </group>
+                    </groups>
+                    <show>private</show>
+                    <links>
+                        <link>http://java.sun.com/j2se/1.4.2/docs/api</link>
+                        <link>http://jakarta.apache.org/commons/logging/apidocs/</link>
+                        <link>http://logging.apache.org/log4j/docs/api/</link>
+                    </links>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-source</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                        </manifest>
+                        <manifestEntries>
+                            <url>${pom.url}</url>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+        <defaultGoal>install</defaultGoal>
+    </build>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+            </plugin>
+            <!--plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.0</version>
+                <configuration>
+                    <aggregate>true</aggregate>
+                </configuration>
+            </plugin-->
+            <plugin>
+                <artifactId>maven-surefire-report-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jxr-maven-plugin</artifactId>
+            </plugin>
+            <!--plugin>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <configuration>
+                    <linkXref>true</linkXref>
+                    <sourceEncoding>utf-8</sourceEncoding>
+                    <minimumTokens>100</minimumTokens>
+                    <targetJdk>1.5</targetJdk>
+                </configuration>
+            </plugin-->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-changes-plugin</artifactId>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>jira-report</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+        </plugins>
+
+    </reporting>
+    <dependencies>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.jms</groupId>
+            <artifactId>jms</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>jgroups</groupId>
+            <artifactId>jgroups-all</artifactId>
+            <version>2.2.8</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate</artifactId>
+            <version>3.2.3.ga</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>3.8.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>httpunit</groupId>
+            <artifactId>httpunit</artifactId>
+            <version>1.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junitperf</groupId>
+            <artifactId>junitperf</artifactId>
+            <version>1.9.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>groboutils</groupId>
+            <artifactId>groboutils-core</artifactId>
+            <version>5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..4d9cdf4
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,62 @@
+OSCACHE
+-------
+OSCache is a high performance and widely used J2EE caching solution. For a
+full list of features, see:
+
+	http://www.opensymphony.com/oscache/features.html
+
+
+REQUIREMENTS
+------------
+OSCache can be used as a standalone caching solution for any Java application.
+OSCache's tag library and the caching filter for dynamic binary content, like
+PDFs or images, require a Servlet 2.3 / JSP 1.2 compliant container.
+
+
+INSTALLATION AND DOCUMENTATION
+-----------
+For installation instructions, see docs/install.html. For futher
+documentation, see docs/index.html.
+
+
+WHAT'S NEW
+-----------
+For a complete list of changes for this and previous releases, see
+docs/changelog.html.
+
+
+FURTHER INFORMATION
+-----------
+For further instructions and documentation see the OSCache website at:
+
+	http://www.opensymphony.com/oscache
+	
+The development of the OSCache project is hosted at:
+
+	http://oscache.dev.java.net
+	
+From there you can access the latest code from CVS, subscribe to the mailing
+lists and search and submit bug reports.
+
+To get help, please ask on the users mailing list, do not email the developers
+directly - they will reply on list to help everyone. The OSCache mailing lists
+are available at
+
+	http://oscache.dev.java.net/servlets/ProjectMailingListList
+	
+Since OSCache was originally hosted at SourceForge, you may also want to
+search the old mailing list archives at:
+
+	http://sourceforge.net/mailarchive/forum.php?forum_id=4754
+
+
+OPENSYMPHONY
+------------
+
+OSCache is part of the OpenSymphony project. For more information about
+OpenSymphony, see:
+
+	http://www.opensymphony.com
+
+
+Enjoy!
\ No newline at end of file
diff --git a/src/etc/META-INF/taglib.tld b/src/etc/META-INF/taglib.tld
new file mode 100644
index 0000000..6c07b57
--- /dev/null
+++ b/src/etc/META-INF/taglib.tld
@@ -0,0 +1,152 @@
+<?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.2</jsp-version>
+    <short-name>oscache</short-name>
+    <uri>http://www.opensymphony.com/oscache</uri>
+    <display-name>OSCache Tag Library</display-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>
+
+    <tag>
+        <name>addgroup</name>
+        <tag-class>com.opensymphony.oscache.web.tag.GroupTag</tag-class>
+        <description>A tag to add a group to an ancestor cache tag</description>
+        <attribute>
+            <name>group</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+    </tag>
+
+    <tag>
+        <name>addgroups</name>
+        <tag-class>com.opensymphony.oscache.web.tag.GroupsTag</tag-class>
+        <description>A tag to add a comma-delimited list of groups to an ancestor cache tag</description>
+        <attribute>
+            <name>groups</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+    </tag>
+    
+</taglib>
diff --git a/src/etc/findBugsExcludeFilter.xml b/src/etc/findBugsExcludeFilter.xml
new file mode 100644
index 0000000..900823e
--- /dev/null
+++ b/src/etc/findBugsExcludeFilter.xml
@@ -0,0 +1,5 @@
+<FindBugsFilter>
+    <Match> 
+      <Class name="*.Test*" />
+    </Match>
+</FindBugsFilter>
\ No newline at end of file
diff --git a/src/etc/jalopy.xml b/src/etc/jalopy.xml
new file mode 100644
index 0000000..fe93b27
--- /dev/null
+++ b/src/etc/jalopy.xml
@@ -0,0 +1,378 @@
+<?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>
+
diff --git a/src/etc/oscache.properties b/src/etc/oscache.properties
new file mode 100644
index 0000000..ccdb677
--- /dev/null
+++ b/src/etc/oscache.properties
@@ -0,0 +1,140 @@
+# CACHE IN MEMORY
+#
+# If you want to disable memory caching, just uncomment this line.
+#
+# 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
+#
+# These hook OSCache events and perform various actions such as logging
+# cache hits and misses, or broadcasting to other cache instances across a cluster.
+# See the documentation for further information.
+#
+# cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroadcastingListener,  \
+#                       com.opensymphony.oscache.extra.CacheEntryEventListenerImpl,               \
+#                       com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl,           \
+#                       com.opensymphony.oscache.extra.ScopeEventListenerImpl,                    \
+#                       com.opensymphony.oscache.extra.StatisticListenerImpl
+
+
+# CACHE PERSISTENCE CLASS
+#
+# Specify the class to use for persistence. If you use the supplied DiskPersistenceListener,
+# don't forget to supply the cache.path property to specify the location of the cache
+# directory.
+# 
+# If a persistence class is not specified, OSCache will use memory caching only.
+#
+# cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+# cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener
+
+# CACHE OVERFLOW PERSISTENCE
+# Use persistent cache in overflow or not. The default value is false, which means
+# the persistent cache will be used at all times for every entry.  true is the recommended setting.
+#
+# cache.persistence.overflow.only=true
+
+# CACHE DIRECTORY
+#
+# This is the directory on disk where caches will be stored by the DiskPersistenceListener.
+# it will be created if it doesn't already exist. Remember that OSCache must have
+# write permission to this directory.
+#
+# Note: for Windows machines, this needs \ to be escaped
+# ie Windows:
+# cache.path=c:\\myapp\\cache
+# or *ix:
+# cache.path=/opt/myapp/cache
+#
+# cache.path=c:\\app\\cache
+
+
+# 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.LRUCache
+# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
+# cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
+
+# THREAD BLOCKING BEHAVIOR
+#
+# When a request is made for a stale cache entry, it is possible that another thread is already
+# in the process of rebuilding that entry. This setting specifies how OSCache handles the
+# subsequent 'non-building' threads. The default behaviour (cache.blocking=false) is to serve
+# the old content to subsequent threads until the cache entry has been updated. This provides
+# the best performance (at the cost of serving slightly stale data). When blocking is enabled,
+# threads will instead block until the new cache entry is ready to be served. Once the new entry
+# is put in the cache the blocked threads will be restarted and given the new entry.
+# Note that even if blocking is disabled, when there is no stale data available to be served
+# threads will block until the data is added to the cache by the thread that is responsible
+# for building the data.
+#
+# cache.blocking=false
+
+# CACHE SIZE
+#
+# Default cache size in number of items. 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. The default value is false, which means
+# the disk cache will be limited in size to the value specified by cache.capacity.
+#
+# cache.unlimited.disk=false
+
+
+# JMS CLUSTER PROPERTIES
+#
+# Configuration properties for JMS clustering. See the clustering documentation
+# for more information on these settings.
+#
+#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
+#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
+#cache.cluster.jms.node.name=node1
+
+
+# JAVAGROUPS 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 clustering documentation and 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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+#UNICAST(timeout=300,600,1200,2400):\
+#pbcast.STABLE(desired_avg_gossip=20000):\
+#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
diff --git a/src/java/com/opensymphony/oscache/base/AbstractCacheAdministrator.java b/src/java/com/opensymphony/oscache/base/AbstractCacheAdministrator.java
new file mode 100644
index 0000000..00d127c
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/AbstractCacheAdministrator.java
@@ -0,0 +1,413 @@
+/*
+ * 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.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: 425 $
+ * @author        a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:fabian.crabus at gurulogic.de">Fabian Crabus</a>
+ * @author <a href="mailto:chris@swebtec.com">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.
+     */
+    public 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.
+     */
+    public 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).
+     */
+    public 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.
+     */
+    public 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).
+     */
+    public 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}.
+     */
+    public static final String PERSISTENCE_CLASS_KEY = "cache.persistence.class";
+
+    /**
+     * A String cache configuration property that specifies if the cache persistence
+     * will only be used in overflow mode, that is, when the memory cache capacity has been reached.
+     */
+    public static final String CACHE_PERSISTENCE_OVERFLOW_KEY = "cache.persistence.overflow.only";
+
+    /**
+     * 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.
+     */
+    public static final String CACHE_ENTRY_EVENT_LISTENERS_KEY = "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_KEY} 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;
+
+    /**
+     * 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 persistent cache should be used immediately or only when the memory capacity
+         * has been reached, ie. overflow only.
+     * This can be set via the {@link #CACHE_PERSISTENCE_OVERFLOW_KEY} configuration property.
+     */
+    private boolean overflowPersistence;
+
+    /**
+     * 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). Administrator implementations
+     * should override this method to ensure that their {@link Cache} objects
+     * are updated correctly (by calling {@link AbstractConcurrentReadCache#setMaxEntries(int)}}}.
+     *
+     * @param newCacheCapacity The new capacity
+     */
+    protected void setCacheCapacity(int newCacheCapacity) {
+        cacheCapacity = newCacheCapacity;
+    }
+
+    /**
+     * Whether entries are cached in memory or not.
+     * 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;
+    }
+
+    /**
+     * Check if we use overflowPersistence
+     *
+     * @return Returns the overflowPersistence.
+     */
+    public boolean isOverflowPersistence() {
+        return this.overflowPersistence;
+    }
+
+    /**
+     * Sets the overflowPersistence flag
+     *
+     * @param overflowPersistence The overflowPersistence to set.
+     */
+    public void setOverflowPersistence(boolean overflowPersistence) {
+        this.overflowPersistence = overflowPersistence;
+    }
+
+    /**
+     * Retrieves an array containing instances all of the {@link CacheEventListener}
+     * classes that are specified in the OSCache configuration file.
+     */
+    protected CacheEventListener[] getCacheEventListeners() {
+        List classes = StringUtil.split(config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY), ',');
+        CacheEventListener[] listeners = new CacheEventListener[classes.size()];
+
+        for (int i = 0; i < classes.size(); i++) {
+            String className = (String) classes.get(i);
+
+            try {
+                Class clazz = Class.forName(className);
+
+                if (!CacheEventListener.class.isAssignableFrom(clazz)) {
+                    log.error("Specified listener class '" + className + "' does not implement CacheEventListener. Ignoring this listener.");
+                } else {
+                    listeners[i] = (CacheEventListener) clazz.newInstance();
+                }
+            } catch (ClassNotFoundException e) {
+                log.error("CacheEventListener class '" + className + "' not found. Ignoring this listener.", e);
+            } catch (InstantiationException e) {
+                log.error("CacheEventListener class '" + className + "' could not be instantiated because it is not a concrete class. Ignoring this listener.", e);
+            } catch (IllegalAccessException e) {
+                log.error("CacheEventListener class '" + className + "' 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_KEY);
+
+        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_KEY) != null) {
+            cache = setPersistenceListener(cache);
+        }
+
+        if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != 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(cache, 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]);
+                } else if (listeners[i] instanceof CacheMapAccessEventListener) {
+                    cache.addCacheEventListener(listeners[i]);
+                }
+            }
+        }
+
+        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) {
+        // It's possible for cache to be null if getCache() was never called (CACHE-63)
+        if (cache == null) {
+            return;
+        }
+
+        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();
+        overflowPersistence = Boolean.valueOf(config.getProperty(CACHE_PERSISTENCE_OVERFLOW_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);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/Cache.java b/src/java/com/opensymphony/oscache/base/Cache.java
new file mode 100644
index 0000000..895c060
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/Cache.java
@@ -0,0 +1,1014 @@
+/*
+ * 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: 468 $
+ * @author <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:tgochenour at peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:chris@swebtec.com">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 map that holds keys of cache entries that are currently being built, and EntryUpdateState instance as values. This is used to coordinate threads
+     * that modify/access a same key in concurrence.
+     * 
+     * The cache checks against this map when a stale entry is requested, or a cache miss is observed.
+     * 
+     * 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>
+     * To avoid data races, values in this map should remain present during the whole time distinct threads deal with the
+     * same key. We implement this using explicit reference counting in the EntryUpdateState instance, to be able to clean up
+     * the map once all threads have declared they are done accessing/updating a given key.
+     * 
+     * It is not possible to locate this into the CacheEntry because this would require to have a CacheEntry instance for all cache misses, and
+     * may therefore generate a memory leak. More over, the CacheEntry instance may not be hold in memory in the case 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;
+    }
+    
+    /**
+     * @return the maximum number of items to cache can hold.
+     */
+    public int getCapacity() {
+        return cacheMap.getMaxEntries();
+    }
+
+    /**
+     * Allows the capacity of the cache to be altered dynamically. Note that
+     * some cache implementations may choose to ignore this setting (eg the
+     * {@link UnlimitedCache} ignores this call).
+     *
+     * @param capacity the maximum number of items to hold in the cache.
+     */
+    public void setCapacity(int capacity) {
+        cacheMap.setMaxEntries(capacity);
+    }
+
+    /**
+     * 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) {
+            final long lastUpdate = cacheEntry.getLastUpdate();
+            final long flushTime = flushDateTime.getTime();
+
+            // CACHE-241: check flushDateTime with current time also
+            return (flushTime <= System.currentTimeMillis()) && (flushTime >= lastUpdate);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Retrieve an object from the cache specifying its key.
+     *
+     * @param key             Key of the object in the cache.
+     *
+     * @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) throws NeedsRefreshException {
+        return getFromCache(key, CacheEntry.INDEFINITE_EXPIRY, 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. To
+     * allow the object to stay in the cache indefinitely, supply a value
+     * of {@link CacheEntry#INDEFINITE_EXPIRY}.
+     *
+     * @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. To
+     * allow the object to stay in the cache indefinitely, supply a value
+     * of {@link CacheEntry#INDEFINITE_EXPIRY}.
+     * @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)) {
+
+            //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
+            EntryUpdateState updateState = getUpdateState(key);
+            try {
+                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. 
+                                // This increments the usage count for this EntryUpdateState instance
+                                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;
+                    }
+                }
+            } finally {
+                //Make sure we release the usage count for this EntryUpdateState since we don't use it anymore. If the current thread started the update, then the counter was
+                //increased by one in startUpdate()
+                releaseUpdateState(updateState, key);
+            }
+        }
+
+        // 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;
+    }
+
+    /**
+     * 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.
+     * @since 2.4
+     */
+    public void addCacheEventListener(CacheEventListener listener) {
+        // listenerList.add(CacheEventListener.class, listener);
+        listenerList.add(listener.getClass(), listener);
+    }
+    
+    /**
+     * 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.
+     * @param clazz the type of the listener to be added
+     * @deprecated use {@link #addCacheEventListener(CacheEventListener)}
+     */
+    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.");
+        }
+    }
+    
+    /**
+     * Returns the list of all CacheEventListeners.
+     * @return the CacheEventListener's list of the Cache
+     */
+    public EventListenerList getCacheEventListenerList() {
+        return listenerList;
+    }
+
+    /**
+     * 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.
+     * @throws IllegalStateException if the cache entry isn't in the state UPDATE_IN_PROGRESS
+     */
+    public void cancelUpdate(String key) {
+        EntryUpdateState state;
+
+        if (key != null) {
+            synchronized (updateStates) {
+                state = (EntryUpdateState) updateStates.get(key);
+
+                if (state != null) {
+                    synchronized (state) {
+                        int usageCounter = state.cancelUpdate();
+                        state.notify();
+                        
+                        checkEntryStateUpdateUsage(key, state, usageCounter);
+                    }
+                } else {
+                    if (log.isErrorEnabled()) {
+                        log.error("internal error: expected to get a state from key [" + key + "]");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Utility method to check if the specified usage count is zero, and if so remove the corresponding EntryUpdateState from the updateStates. This is designed to factor common code.
+     * 
+     * Warning: This method should always be called while holding both the updateStates field and the state parameter
+     * @throws Exception
+     */
+    private void checkEntryStateUpdateUsage(String key, EntryUpdateState state, int usageCounter) {
+        //Clean up the updateStates map to avoid a memory leak once no thread is using this EntryUpdateState instance anymore.
+        if (usageCounter == 0) {
+            EntryUpdateState removedState = (EntryUpdateState) updateStates.remove(key);
+            if (state != removedState) {
+                if (log.isErrorEnabled()) {
+                    try {
+                        throw new Exception("OSCache: internal error: removed state [" + removedState + "] from key [" + key + "] whereas we expected [" + state + "]");
+                    } catch (Exception e) {
+                        log.error(e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 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;
+
+        if (listenerList.getListenerCount() > 0) {
+            dispatchCachewideEvent(CachewideEventType.CACHE_FLUSHED, date, 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);
+                }
+            }
+        }
+
+        if (listenerList.getListenerCount() > 0) {
+            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);
+                    }
+                }
+            }
+
+            if (listenerList.getListenerCount() > 0) {
+                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();
+
+        // [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);
+            }
+        }
+    }
+
+    /**
+     * Unregister a listener for Cache events.
+     *
+     * @param listener  The object that currently listens to events.
+     * @param clazz  The registrated class of listening object.
+     * @deprecated use instead {@link #removeCacheEventListener(CacheEventListener)}
+     */
+    public void removeCacheEventListener(CacheEventListener listener, Class clazz) {
+        listenerList.remove(clazz, listener);
+    }
+
+    /**
+     * Unregister a listener for Cache events.
+     *
+     * @param listener  The object that currently listens to events.
+     * @since 2.4
+     */
+    public void removeCacheEventListener(CacheEventListener listener) {
+        // listenerList.remove(CacheEventListener.class, listener);
+        listenerList.remove(listener.getClass(), 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 ((!result) && (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);
+            } else {
+                //Otherwise indicate that we start using it to prevent its removal until all threads are done with it.
+                updateState.incrementUsageCounter();
+            }
+        }
+
+        return updateState;
+    }
+
+    /**
+     * releases the usage that was made of the specified EntryUpdateState. When this reaches zero, the entry is removed from the map. 
+     * @param state the state to release the usage of
+     * @param key the associated key.
+     */
+    protected void releaseUpdateState(EntryUpdateState state, String key) {
+        synchronized (updateStates) {
+            int usageCounter = state.decrementUsageCounter();
+            checkEntryStateUpdateUsage(key, state, usageCounter);
+        }       
+    }
+    
+    /**
+     * 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, so it is possible that no EntryUpdateState was hold
+     * when this method is called.
+     *
+     * @param key The cache key that is no longer being updated.
+     */
+    protected void completeUpdate(String key) {
+        EntryUpdateState state;
+
+        synchronized (updateStates) {
+            state = (EntryUpdateState) updateStates.get(key);
+
+            if (state != null) {
+                synchronized (state) {
+                    int usageCounter = state.completeUpdate();
+                    state.notifyAll();
+                    
+                    checkEntryStateUpdateUsage(key, state, usageCounter);
+
+                }
+            } else {
+                //If putInCache() was called directly (i.e. not as a result of a NeedRefreshException) then no EntryUpdateState would be found. 
+            }
+        }
+    }
+
+    /**
+     * Completely removes a cache entry from the cache and its associated cache
+     * groups.
+     *
+     * @param key The key of the entry to remove.
+     */
+    public 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);
+
+        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+1] instanceof CacheEntryEventListener) {
+                CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i+1];
+                if (eventType.equals(CacheEntryEventType.ENTRY_ADDED)) {
+                    listener.cacheEntryAdded(event);
+                } else if (eventType.equals(CacheEntryEventType.ENTRY_UPDATED)) {
+                    listener.cacheEntryUpdated(event);
+                } else if (eventType.equals(CacheEntryEventType.ENTRY_FLUSHED)) {
+                    listener.cacheEntryFlushed(event);
+                } else if (eventType.equals(CacheEntryEventType.ENTRY_REMOVED)) {
+                    listener.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+1] instanceof CacheEntryEventListener) {
+                CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i + 1];
+                if (eventType.equals(CacheEntryEventType.GROUP_FLUSHED)) {
+                    listener.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+1] instanceof CacheMapAccessEventListener) {
+                CacheMapAccessEventListener listener = (CacheMapAccessEventListener) listeners[i + 1];
+                listener.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+1] instanceof CacheEntryEventListener) {
+                if (eventType.equals(CacheEntryEventType.PATTERN_FLUSHED)) {
+                    CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i+1];
+                    listener.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+1] instanceof CacheEntryEventListener) {
+                if (eventType.equals(CachewideEventType.CACHE_FLUSHED)) {
+                    CacheEntryEventListener listener = (CacheEntryEventListener) listeners[i+1];
+                    listener.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);
+        }
+    }
+    
+    /**
+     * @return the total number of cache entries held in this cache. 
+     */
+    public int getSize() {
+        synchronized(cacheMap) {
+            return cacheMap.size();
+        }
+    }
+
+    /**
+     * Test support only: return the number of EntryUpdateState instances within the updateStates map. 
+     */
+    protected int getNbUpdateState() {
+        synchronized(updateStates) {
+            return updateStates.size();
+        }
+    }
+    
+    
+    /**
+     * Test support only: return the number of entries currently in the cache map
+     * @deprecated use getSize() 
+     */
+    public int getNbEntries() {
+        synchronized(cacheMap) {
+            return cacheMap.size();
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/CacheEntry.java b/src/java/com/opensymphony/oscache/base/CacheEntry.java
new file mode 100644
index 0000000..e9f504b
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/CacheEntry.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.web.filter.ResponseContent;
+
+import java.io.Serializable;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A CacheEntry instance represents one entry in the cache. It holds the object that
+ * is being cached, along with a host of information about that entry such as the
+ * cache key, the time it was cached, whether the entry has been flushed or not and
+ * the groups it belongs to.
+ *
+ * @version        $Revision: 388 $
+ * @author        <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author        <a href="mailto:tgochenour at peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+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.
+     */
+    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.
+     */
+    public static final int INDEFINITE_EXPIRY = -1;
+
+    /**
+     * The entry refresh policy object to use for this cache entry. This is optional.
+     */
+    private EntryRefreshPolicy policy = null;
+
+    /**
+     * 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.
+     */
+    private Set groups = null;
+
+    /**
+     *  The unique cache key for this entry
+     */
+    private String key;
+
+    /**
+     * <code>true</code> if this entry was flushed
+     */
+    private boolean wasFlushed = false;
+
+    /**
+     * The time this entry was created.
+     */
+    private long created = NOT_YET;
+
+    /**
+     * 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
+     */
+    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.
+     */
+    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.
+     */
+    public CacheEntry(String key, EntryRefreshPolicy policy, String[] groups) {
+        this.key = key;
+
+        if (groups != null) {
+            this.groups = new HashSet(groups.length);
+
+            for (int i = 0; i < groups.length; i++) {
+                this.groups.add(groups[i]);
+            }
+        }
+
+        this.policy = policy;
+        this.created = System.currentTimeMillis();
+    }
+
+    /**
+     * 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();
+        wasFlushed = false;
+    }
+
+    /**
+     * 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.
+     */
+    public long getCreated() {
+        return created;
+    }
+
+    /**
+     * 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);
+
+            for (int i = 0; i < groups.length; i++) {
+                this.groups.add(groups[i]);
+            }
+        } else {
+            this.groups = null;
+        }
+
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * Sets the cache groups for this entry
+     *
+     * @param groups A collection containing all the group names
+     */
+    public synchronized void setGroups(Collection groups) {
+        if (groups != null) {
+            this.groups = new HashSet(groups);
+        } else {
+            this.groups = null;
+        }
+
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * 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
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * 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.
+     */
+    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
+     */
+    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.
+     */
+    public int getSize() {
+        // a char is two bytes
+        int size = (key.length() * 2) + 4;
+
+        if (content.getClass() == String.class) {
+            size += ((content.toString().length() * 2) + 4);
+        } else if (content instanceof ResponseContent) {
+            size += ((ResponseContent) content).getSize();
+        } else {
+            return -1;
+        }
+
+        //add created, lastUpdate, and wasFlushed field sizes (1, 8, and 8)
+        return size + 17;
+    }
+
+    /**
+     * 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.
+     */
+    public boolean needsRefresh(int refreshPeriod) {
+        boolean needsRefresh;
+
+        // needs a refresh if it has never been updated
+        if (lastUpdate == NOT_YET) {
+            needsRefresh = true;
+        }
+        // Was it flushed from cache?
+        else if (wasFlushed) {
+            needsRefresh = true;
+        } else if (refreshPeriod == 0) {
+            needsRefresh = true;
+        }
+        // check what the policy has to say if there is one
+        else if (policy != null) {
+            needsRefresh = policy.needsRefresh(this);
+        }
+        // check if the last update + update period is in the past
+        else if ((refreshPeriod >= 0) && (System.currentTimeMillis() >= (lastUpdate + (refreshPeriod * 1000L)))) {
+            needsRefresh = true;
+        } else {
+            needsRefresh = false;
+        }
+
+        return needsRefresh;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/Config.java b/src/java/com/opensymphony/oscache/base/Config.java
new file mode 100644
index 0000000..c6f3e29
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/Config.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import java.util.Properties;
+
+/**
+ * Responsible for holding the Cache configuration properties. If the default
+ * constructor is used, this class will load the properties from the
+ * <code>cache.configuration</code>.
+ *
+ * @author   <a href="mailto:fabian.crabus at gurulogic.de">Fabian Crabus</a>
+ * @version  $Revision: 412 $
+ */
+public class Config implements java.io.Serializable {
+    
+    private static final transient Log log = LogFactory.getLog(Config.class);
+
+    /**
+     * Name of the properties file.
+     */
+    private final static String PROPERTIES_FILENAME = "/oscache.properties";
+
+    /**
+     * Properties map to hold the cache configuration.
+     */
+    private Properties properties = null;
+
+    /**
+     * Create an OSCache Config that loads properties from oscache.properties.
+     * The file must be present in the root of OSCache's classpath. If the file
+     * cannot be loaded, an error will be logged and the configuration will
+     * remain empty.
+     */
+    public Config() {
+        this(null);
+    }
+
+    /**
+     * Create an OSCache configuration with the specified properties.
+     * Note that it is the responsibility of the caller to provide valid
+     * properties as no error checking is done to ensure that required
+     * keys are present. If you're unsure of what keys should be present,
+     * have a look at a sample oscache.properties file.
+     *
+     * @param p The properties to use for this configuration. If null,
+     * then the default properties are loaded from the <code>oscache.properties</code>
+     * file.
+     */
+    public Config(Properties p) {
+        if (log.isDebugEnabled()) {
+            log.debug("OSCache: Config called");
+        }
+
+        if (p == null) {
+            this.properties = loadProperties(PROPERTIES_FILENAME, "the default configuration");
+        } else {
+            this.properties = p;
+        }
+    }
+
+    /**
+     * Retrieve the value of the named configuration property. If the property
+     * cannot be found this method will return <code>null</code>.
+     *
+     * @param key The name of the property.
+     * @return The property value, or <code>null</code> if the value could
+     * not be found.
+     *
+     * @throws IllegalArgumentException if the supplied key is null.
+     */
+    public String getProperty(String key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key is null");
+        }
+
+        if (properties == null) {
+            return null;
+        }
+
+        return properties.getProperty(key);
+    }
+
+    /**
+     * Retrieves all of the configuration properties. This property set
+     * should be treated as immutable.
+     *
+     * @return The configuration properties.
+     */
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public Object get(Object key) {
+        return properties.get(key);
+    }
+
+    /**
+     * Sets a configuration property.
+     *
+     * @param key   The unique name for this property.
+     * @param value The value assigned to this property.
+     *
+     * @throws IllegalArgumentException if the supplied key is null.
+     */
+    public void set(Object key, Object value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key is null");
+        }
+
+        if (value == null) {
+            return;
+        }
+
+        if (properties == null) {
+            properties = new Properties();
+        }
+
+        properties.put(key, value);
+    }
+    
+    /**
+     * Load the properties from the specified URL.
+     * @param url a non null value of the URL to the properties
+     * @param info additional logger information if the properties can't be read
+     * @return the loaded properties specified by the URL
+     * @since 2.4
+     */
+    public static Properties loadProperties(URL url, String info) {
+        log.info("OSCache: Getting properties from URL " + url + " for " + info);
+
+        Properties properties = new Properties();
+        InputStream in = null;
+
+        try {
+            in = url.openStream();
+            properties.load(in);
+            log.info("OSCache: Properties read " + properties);
+        } catch (Exception e) {
+            log.error("OSCache: Error reading from " + url, e);
+            log.error("OSCache: Ensure the properties information in " + url+ " is readable and in your classpath.");
+        } finally {
+            try {
+                in.close();
+            } catch (IOException e) {
+                log.warn("OSCache: IOException while closing InputStream: " + e.getMessage());
+            }
+        }
+        
+        return properties;
+    }
+
+    /**
+     * Load the specified properties file from the classpath. If the file
+     * cannot be found or loaded, an error will be logged and no
+     * properties will be set.
+     * @param filename the properties file with path
+     * @param info additional logger information if file can't be read
+     * @return the loaded properties specified by the filename
+     * @since 2.4
+     */
+    public static Properties loadProperties(String filename, String info) {
+        URL url = null;
+        
+        ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
+        if (threadContextClassLoader != null) {
+            url = threadContextClassLoader.getResource(filename);
+        }
+        if (url == null) {
+            url = Config.class.getResource(filename);
+            if (url == null) {
+                log.warn("OSCache: No properties file found in the classpath by filename " + filename);
+                return new Properties();
+            }
+        }
+        
+        return loadProperties(url, info);
+    }
+
+}
diff --git a/src/java/com/opensymphony/oscache/base/EntryRefreshPolicy.java b/src/java/com/opensymphony/oscache/base/EntryRefreshPolicy.java
new file mode 100644
index 0000000..9c4311f
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/EntryRefreshPolicy.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import java.io.Serializable;
+
+/**
+ * Interface that allows custom code to be called when checking to see if a cache entry
+ * has expired. This is useful when the rules that determine when content needs refreshing
+ * are beyond the base funtionality offered by OSCache.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface EntryRefreshPolicy extends Serializable {
+    /**
+     * Indicates whether the supplied <code>CacheEntry</code> needs to be refreshed.
+     * This will be called when retrieving an entry from the cache - if this method
+     * returns <code>true</code> then a <code>NeedsRefreshException</code> will be
+     * thrown.
+     *
+     * @param entry The cache entry that is being tested.
+     * @return <code>true</code> if the content needs refreshing, <code>false</code> otherwise.
+     *
+     * @see NeedsRefreshException
+     * @see CacheEntry
+     */
+    public boolean needsRefresh(CacheEntry entry);
+}
diff --git a/src/java/com/opensymphony/oscache/base/EntryUpdateState.java b/src/java/com/opensymphony/oscache/base/EntryUpdateState.java
new file mode 100644
index 0000000..01e1c80
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/EntryUpdateState.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+
+/**
+ * Holds the state of a Cache Entry that is in the process of being (re)generated.
+ * This is not synchronized; the synchronization must be handled by the calling
+ * classes.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @author Author: $
+ * @version Revision: $
+ */
+public class EntryUpdateState {
+    /**
+     * The initial state when this object is first created
+     */
+    public static final int NOT_YET_UPDATING = -1;
+
+    /**
+     * Update in progress state
+     */
+    public static final int UPDATE_IN_PROGRESS = 0;
+
+    /**
+     * Update complete state
+     */
+    public static final int UPDATE_COMPLETE = 1;
+
+    /**
+     * Update cancelled state
+     */
+    public static final int UPDATE_CANCELLED = 2;
+
+    /**
+     * Current update state
+     */
+    int state = NOT_YET_UPDATING;
+    
+    /**
+     * A counter of the number of threads that are coordinated through this instance. When this counter gets to zero, then the reference to this
+     * instance may be released from the Cache instance.
+     * This is counter is protected by the EntryStateUpdate instance monitor.
+     */
+    private int nbConcurrentUses = 1;
+
+    /**
+     * This is the initial state when an instance this object is first created.
+     * It indicates that a cache entry needs updating, but no thread has claimed
+     * responsibility for updating it yet.
+     */
+    public boolean isAwaitingUpdate() {
+        return state == NOT_YET_UPDATING;
+    }
+
+    /**
+     * The thread that was responsible for updating the cache entry (ie, the thread
+     * that managed to grab the update lock) has decided to give up responsibility
+     * for performing the update. OSCache will notify any other threads that are
+     * waiting on the update so one of them can take over the responsibility.
+     */
+    public boolean isCancelled() {
+        return state == UPDATE_CANCELLED;
+    }
+
+    /**
+     * The update of the cache entry has been completed.
+     */
+    public boolean isComplete() {
+        return state == UPDATE_COMPLETE;
+    }
+
+    /**
+     * The cache entry is currently being generated by the thread that got hold of
+     * the update lock.
+     */
+    public boolean isUpdating() {
+        return state == UPDATE_IN_PROGRESS;
+    }
+
+    /**
+     * Updates the state to <code>UPDATE_CANCELLED</code>. This should <em>only<em>
+     * be called by the thread that managed to get the update lock.
+     * @return the counter value after the operation completed
+     */
+    public int cancelUpdate() {
+        if (state != UPDATE_IN_PROGRESS) {
+            throw new IllegalStateException("Cannot cancel cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS");
+        }
+
+        state = UPDATE_CANCELLED;
+        return decrementUsageCounter();
+    }
+
+    /**
+     * Updates the state to <code>UPDATE_COMPLETE</code>. This should <em>only</em>
+     * be called by the thread that managed to get the update lock.
+     * @return the counter value after the operation completed
+     */
+    public int completeUpdate() {
+        if (state != UPDATE_IN_PROGRESS) {
+            throw new IllegalStateException("Cannot complete cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS");
+        }
+
+        state = UPDATE_COMPLETE;
+        return decrementUsageCounter();
+    }
+
+    /**
+     * Attempt to change the state to <code>UPDATE_IN_PROGRESS</code>. Calls
+     * to this method must be synchronized on the EntryUpdateState instance.
+     * @return the counter value after the operation completed
+     */
+    public int startUpdate() {
+        if ((state != NOT_YET_UPDATING) && (state != UPDATE_CANCELLED)) {
+            throw new IllegalStateException("Cannot begin cache update - current state (" + state + ") is not NOT_YET_UPDATING or UPDATE_CANCELLED");
+        }
+
+        state = UPDATE_IN_PROGRESS;
+        return incrementUsageCounter();
+    }
+
+    /**
+     * Increments the usage counter by one
+     * @return the counter value after the increment
+     */
+	public synchronized int incrementUsageCounter() {
+		nbConcurrentUses++;
+		return nbConcurrentUses;
+	}
+	
+    /**
+     * Gets the current usage counter value
+     * @return a positive number.
+     */
+	public synchronized int getUsageCounter() {
+		return nbConcurrentUses;
+	}
+	
+	
+    /**
+     * Decrements the usage counter by one. This method may only be called when the usage number is greater than zero
+     * @return the counter value after the decrement
+     */
+	public synchronized int decrementUsageCounter() {
+		if (nbConcurrentUses <=0) {
+            throw new IllegalStateException("Cannot decrement usage counter, it is already equals to [" + nbConcurrentUses + "]");
+		}
+		nbConcurrentUses--;
+		return nbConcurrentUses;
+	}
+
+	
+}
diff --git a/src/java/com/opensymphony/oscache/base/FinalizationException.java b/src/java/com/opensymphony/oscache/base/FinalizationException.java
new file mode 100644
index 0000000..bb91c3b
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/FinalizationException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+
+/**
+ * Thrown by {@link LifecycleAware} listeners that are not able to finalize
+ * themselves.
+ *
+ * @version $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class FinalizationException extends Exception {
+    public FinalizationException() {
+        super();
+    }
+
+    public FinalizationException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/InitializationException.java b/src/java/com/opensymphony/oscache/base/InitializationException.java
new file mode 100644
index 0000000..4439f54
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/InitializationException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+
+/**
+ * Thrown by {@link LifecycleAware} listeners that are not able to initialize
+ * themselves.
+ *
+ * @version $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class InitializationException extends Exception {
+    public InitializationException() {
+        super();
+    }
+
+    public InitializationException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/LifecycleAware.java b/src/java/com/opensymphony/oscache/base/LifecycleAware.java
new file mode 100644
index 0000000..71650d7
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/LifecycleAware.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+
+/**
+ * Event handlers implement this so they can be notified when a cache
+ * is created and also when it is destroyed. This allows event handlers
+ * to load any configuration and/or resources they need on startup and
+ * then release them again when the cache is shut down.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ *
+ * @see com.opensymphony.oscache.base.events.CacheEventListener
+ */
+public interface LifecycleAware {
+    /**
+    * Called by the cache administrator class when a cache is instantiated.
+    *
+    * @param cache the cache instance that this listener is attached to.
+    * @param config The cache's configuration details. This allows the event handler
+    * to initialize itself based on the cache settings, and also to receive <em>additional</em>
+    * settings that were part of the cache configuration but that the cache
+    * itself does not care about. If you are using <code>cache.properties</code>
+    * for your configuration, simply add any additional properties that your event
+    * handler requires and they will be passed through in this parameter.
+    *
+    * @throws InitializationException thrown when there was a problem initializing the
+    * listener. The cache administrator will log this error and disable the listener.
+    */
+    public void initialize(Cache cache, Config config) throws InitializationException;
+
+    /**
+    * Called by the cache administrator class when a cache is destroyed.
+    *
+    * @throws FinalizationException thrown when there was a problem finalizing the
+    * listener. The cache administrator will catch and log this error.
+    */
+    public void finialize() throws FinalizationException;
+}
diff --git a/src/java/com/opensymphony/oscache/base/NeedsRefreshException.java b/src/java/com/opensymphony/oscache/base/NeedsRefreshException.java
new file mode 100644
index 0000000..feb3690
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/NeedsRefreshException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+/**
+ * This exception is thrown when retrieving an item from cache and it is
+ * expired.
+ * Note that for fault tolerance purposes, it is possible to retrieve the
+ * current cached object from the exception.
+ *
+ * <p>January, 2004 - The OSCache developers are aware of the fact that throwing
+ * an exception for a perfect valid situation (cache miss) is design smell. This will
+ * be removed in the near future, and other means of refreshing the cache will be
+ * provided.</p>
+ *
+ * @author        <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @version        $Revision: 433 $
+ */
+public final class NeedsRefreshException extends Exception {
+
+    /**
+     * Current object in the cache
+     */
+    private Object cacheContent = null;
+    
+    /**
+     * Create a NeedsRefreshException
+     */
+    public NeedsRefreshException(String message, Object cacheContent) {
+        super(message);
+        this.cacheContent = cacheContent;
+    }
+
+    /**
+     * Create a NeedsRefreshException
+     */
+    public NeedsRefreshException(Object cacheContent) {
+        super();
+        this.cacheContent = cacheContent;
+    }
+    
+    /**
+     * Retrieve current object in the cache
+     */
+    public Object getCacheContent() {
+        return cacheContent;
+    }
+
+}
diff --git a/src/java/com/opensymphony/oscache/base/algorithm/AbstractConcurrentReadCache.java b/src/java/com/opensymphony/oscache/base/algorithm/AbstractConcurrentReadCache.java
new file mode 100644
index 0000000..3d6e8ad
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/algorithm/AbstractConcurrentReadCache.java
@@ -0,0 +1,2157 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+        File: AbstractConcurrentReadCache
+
+        Written by Doug Lea. Adapted from JDK1.2 HashMap.java and Hashtable.java
+        which carries the following copyright:
+
+                 * Copyright 1997 by Sun Microsystems, Inc.,
+                 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
+                 * All rights reserved.
+                 *
+                 * This software is the confidential and proprietary information
+                 * of Sun Microsystems, Inc. ("Confidential Information").  You
+                 * shall not disclose such Confidential Information and shall use
+                 * it only in accordance with the terms of the license agreement
+                 * you entered into with Sun.
+
+        This class is a modified version of ConcurrentReaderHashMap, which was written
+        by Doug Lea (http://gee.cs.oswego.edu/dl/). The modifications where done
+        by Pyxis Technologies. This is a base class for the OSCache module of the
+        openSymphony project (www.opensymphony.com).
+
+        History:
+        Date       Who                What
+        28oct1999  dl               Created
+        14dec1999  dl               jmm snapshot
+        19apr2000  dl               use barrierLock
+        12jan2001  dl               public release
+        Oct2001    abergevin at pyxis-tech.com
+                                                                Integrated persistence and outer algorithm support
+*/
+package com.opensymphony.oscache.base.algorithm;
+
+
+/** OpenSymphony BEGIN */
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.persistence.CachePersistenceException;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import java.util.*;
+
+/**
+ * A version of Hashtable that supports mostly-concurrent reading, but exclusive writing.
+ * Because reads are not limited to periods
+ * without writes, a concurrent reader policy is weaker than a classic
+ * reader/writer policy, but is generally faster and allows more
+ * concurrency. This class is a good choice especially for tables that
+ * are mainly created by one thread during the start-up phase of a
+ * program, and from then on, are mainly read (with perhaps occasional
+ * additions or removals) in many threads.  If you also need concurrency
+ * among writes, consider instead using ConcurrentHashMap.
+ * <p>
+ *
+ * Successful retrievals using get(key) and containsKey(key) usually
+ * run without locking. Unsuccessful ones (i.e., when the key is not
+ * present) do involve brief synchronization (locking).  Also, the
+ * size and isEmpty methods are always synchronized.
+ *
+ * <p> Because retrieval operations can ordinarily overlap with
+ * writing operations (i.e., put, remove, and their derivatives),
+ * retrievals can only be guaranteed to return the results of the most
+ * recently <em>completed</em> operations holding upon their
+ * onset. Retrieval operations may or may not return results
+ * reflecting in-progress writing operations.  However, the retrieval
+ * operations do always return consistent results -- either those
+ * holding before any single modification or after it, but never a
+ * nonsense result.  For aggregate operations such as putAll and
+ * clear, concurrent reads may reflect insertion or removal of only
+ * some entries. In those rare contexts in which you use a hash table
+ * to synchronize operations across threads (for example, to prevent
+ * reads until after clears), you should either encase operations
+ * in synchronized blocks, or instead use java.util.Hashtable.
+ *
+ * <p>
+ *
+ * This class also supports optional guaranteed
+ * exclusive reads, simply by surrounding a call within a synchronized
+ * block, as in <br>
+ * <code>AbstractConcurrentReadCache t; ... Object v; <br>
+ * synchronized(t) { v = t.get(k); } </code> <br>
+ *
+ * But this is not usually necessary in practice. For
+ * example, it is generally inefficient to write:
+ *
+ * <pre>
+ *   AbstractConcurrentReadCache t; ...            // Inefficient version
+ *   Object key; ...
+ *   Object value; ...
+ *   synchronized(t) {
+ *     if (!t.containsKey(key))
+ *       t.put(key, value);
+ *       // other code if not previously present
+ *     }
+ *     else {
+ *       // other code if it was previously present
+ *     }
+ *   }
+ *</pre>
+ * Instead, just take advantage of the fact that put returns
+ * null if the key was not previously present:
+ * <pre>
+ *   AbstractConcurrentReadCache t; ...                // Use this instead
+ *   Object key; ...
+ *   Object value; ...
+ *   Object oldValue = t.put(key, value);
+ *   if (oldValue == null) {
+ *     // other code if not previously present
+ *   }
+ *   else {
+ *     // other code if it was previously present
+ *   }
+ *</pre>
+ * <p>
+ *
+ * Iterators and Enumerations (i.e., those returned by
+ * keySet().iterator(), entrySet().iterator(), values().iterator(),
+ * keys(), and elements()) return elements reflecting the state of the
+ * hash table at some point at or since the creation of the
+ * iterator/enumeration.  They will return at most one instance of
+ * each element (via next()/nextElement()), but might or might not
+ * reflect puts and removes that have been processed since they were
+ * created.  They do <em>not</em> throw ConcurrentModificationException.
+ * However, these iterators are designed to be used by only one
+ * thread at a time. Sharing an iterator across multiple threads may
+ * lead to unpredictable results if the table is being concurrently
+ * modified.  Again, you can ensure interference-free iteration by
+ * enclosing the iteration in a synchronized block.  <p>
+ *
+ * This class may be used as a direct replacement for any use of
+ * java.util.Hashtable that does not depend on readers being blocked
+ * during updates. Like Hashtable but unlike java.util.HashMap,
+ * this class does NOT allow <tt>null</tt> to be used as a key or
+ * value.  This class is also typically faster than ConcurrentHashMap
+ * when there is usually only one thread updating the table, but
+ * possibly many retrieving values from it.
+ * <p>
+ *
+ * Implementation note: A slightly faster implementation of
+ * this class will be possible once planned Java Memory Model
+ * revisions are in place.
+ *
+ * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
+ **/
+public abstract class AbstractConcurrentReadCache extends AbstractMap implements Map, Cloneable, Serializable {
+    /**
+     * The default initial number of table slots for this table (32).
+     * Used when not otherwise specified in constructor.
+     **/
+    public static final int DEFAULT_INITIAL_CAPACITY = 32;
+
+    /**
+     * The minimum capacity.
+     * Used if a lower value is implicitly specified
+     * by either of the constructors with arguments.
+     * MUST be a power of two.
+     */
+    private static final int MINIMUM_CAPACITY = 4;
+
+    /**
+     * The maximum capacity.
+     * Used if a higher value is implicitly specified
+     * by either of the constructors with arguments.
+     * MUST be a power of two <= 1<<30.
+     */
+    private static final int MAXIMUM_CAPACITY = 1 << 30;
+
+    /**
+     * The default load factor for this table.
+     * Used when not otherwise specified in constructor, the default is 0.75f.
+     **/
+    public static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+    //OpenSymphony BEGIN (pretty long!)
+    protected static final String NULL = "_nul!~";
+    
+    private static final Log log = LogFactory.getLog(AbstractConcurrentReadCache.class);
+
+    /*
+      The basic strategy is an optimistic-style scheme based on
+      the guarantee that the hash table and its lists are always
+      kept in a consistent enough state to be read without locking:
+
+      * Read operations first proceed without locking, by traversing the
+         apparently correct list of the apparently correct bin. If an
+         entry is found, but not invalidated (value field null), it is
+         returned. If not found, operations must recheck (after a memory
+         barrier) to make sure they are using both the right list and
+         the right table (which can change under resizes). If
+         invalidated, reads must acquire main update lock to wait out
+         the update, and then re-traverse.
+
+      * All list additions are at the front of each bin, making it easy
+         to check changes, and also fast to traverse.  Entry next
+         pointers are never assigned. Remove() builds new nodes when
+         necessary to preserve this.
+
+      * Remove() (also clear()) invalidates removed nodes to alert read
+         operations that they must wait out the full modifications.
+
+    */
+
+    /**
+     * Lock used only for its memory effects. We use a Boolean
+     * because it is serializable, and we create a new one because
+     * we need a unique object for each cache instance.
+     **/
+    protected final Boolean barrierLock = new Boolean(true);
+
+    /**
+     * field written to only to guarantee lock ordering.
+     **/
+    protected transient Object lastWrite;
+
+    /**
+     * The hash table data.
+     */
+    protected transient Entry[] table;
+
+    /**
+     * The total number of mappings in the hash table.
+     */
+    protected transient int count;
+
+    /**
+     * Persistence listener.
+     */
+    protected transient PersistenceListener persistenceListener = null;
+
+    /**
+     * Use memory cache or not.
+     */
+    protected boolean memoryCaching = true;
+
+    /**
+     * Use unlimited disk caching.
+     */
+    protected boolean unlimitedDiskCache = false;
+
+    /**
+     * The load factor for the hash table.
+     *
+     * @serial
+     */
+    protected float loadFactor;
+
+    /**
+     * Default cache capacity (number of entries).
+     */
+    protected final int DEFAULT_MAX_ENTRIES = 100;
+
+    /**
+     * Max number of element in cache when considered unlimited.
+     */
+    protected final int UNLIMITED = 2147483646;
+    protected transient Collection values = null;
+
+    /**
+     * A HashMap containing the group information.
+     * Each entry uses the group name as the key, and holds a
+     * <code>Set</code> of containing keys of all
+     * the cache entries that belong to that particular group.
+     */
+    protected HashMap groups = new HashMap();
+    protected transient Set entrySet = null;
+
+    // Views
+    protected transient Set keySet = null;
+
+    /**
+     * Cache capacity (number of entries).
+     */
+    protected int maxEntries = DEFAULT_MAX_ENTRIES;
+
+    /**
+     * The table is rehashed when its size exceeds this threshold.
+     * (The value of this field is always (int)(capacity * loadFactor).)
+     *
+     * @serial
+     */
+    protected int threshold;
+
+    /**
+     * Use overflow persistence caching.
+     */
+    private boolean overflowPersistence = false;
+
+    /**
+     * Constructs a new, empty map with the specified initial capacity and the specified load factor.
+     *
+     * @param initialCapacity the initial capacity
+     *  The actual initial capacity is rounded to the nearest power of two.
+     * @param loadFactor  the load factor of the AbstractConcurrentReadCache
+     * @throws IllegalArgumentException  if the initial maximum number
+     *               of elements is less
+     *               than zero, or if the load factor is nonpositive.
+     */
+    public AbstractConcurrentReadCache(int initialCapacity, float loadFactor) {
+        if (loadFactor <= 0) {
+            throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
+        }
+
+        this.loadFactor = loadFactor;
+
+        int cap = p2capacity(initialCapacity);
+        table = new Entry[cap];
+        threshold = (int) (cap * loadFactor);
+    }
+
+    /**
+     * Constructs a new, empty map with the specified initial capacity and default load factor.
+     *
+     * @param   initialCapacity   the initial capacity of the
+     *                            AbstractConcurrentReadCache.
+     * @throws    IllegalArgumentException if the initial maximum number
+     *              of elements is less
+     *              than zero.
+     */
+    public AbstractConcurrentReadCache(int initialCapacity) {
+        this(initialCapacity, DEFAULT_LOAD_FACTOR);
+    }
+
+    /**
+     * Constructs a new, empty map with a default initial capacity and load factor.
+     */
+    public AbstractConcurrentReadCache() {
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
+    }
+
+    /**
+     * Constructs a new map with the same mappings as the given map.
+     * The map is created with a capacity of twice the number of mappings in
+     * the given map or 11 (whichever is greater), and a default load factor.
+     */
+    public AbstractConcurrentReadCache(Map t) {
+        this(Math.max(2 * t.size(), 11), DEFAULT_LOAD_FACTOR);
+        putAll(t);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this map contains no key-value mappings.
+     *
+     * @return <tt>true</tt> if this map contains no key-value mappings.
+     */
+    public synchronized boolean isEmpty() {
+        return count == 0;
+    }
+
+    /**
+     * Returns a set of the cache keys that reside in a particular group.
+     *
+     * @param   groupName The name of the group to retrieve.
+     * @return  a set containing all of the keys of cache entries that belong
+     * to this group, or <code>null</code> if the group was not found.
+     * @exception  NullPointerException if the groupName is <code>null</code>.
+     */
+    public Set getGroup(String groupName) {
+        if (log.isDebugEnabled()) {
+            log.debug("getGroup called (group=" + groupName + ")");
+        }
+
+        Set groupEntries = null;
+
+        if (memoryCaching && (groups != null)) {
+            groupEntries = (Set) getGroupForReading(groupName);
+        }
+
+        if (groupEntries == null) {
+            // Not in the map, try the persistence layer
+            groupEntries = persistRetrieveGroup(groupName);
+        }
+
+        return groupEntries;
+    }
+
+    /**
+     * Set the cache capacity
+     */
+    public void setMaxEntries(int newLimit) {
+        if (newLimit > 0) {
+            maxEntries = newLimit;
+
+            synchronized (this) { // because remove() isn't synchronized
+
+                while (size() > maxEntries) {
+                    remove(removeItem(), false, false);
+                }
+            }
+        } else {
+            // Capacity must be at least 1
+            throw new IllegalArgumentException("Cache maximum number of entries must be at least 1");
+        }
+    }
+
+    /**
+     * Retrieve the cache capacity (number of entries).
+     */
+    public int getMaxEntries() {
+        return maxEntries;
+    }
+
+    /**
+     * Sets the memory caching flag.
+     */
+    public void setMemoryCaching(boolean memoryCaching) {
+        this.memoryCaching = memoryCaching;
+    }
+
+    /**
+     * Check if memory caching is used.
+     */
+    public boolean isMemoryCaching() {
+        return memoryCaching;
+    }
+
+    /**
+     * Set the persistence listener to use.
+     */
+    public void setPersistenceListener(PersistenceListener listener) {
+        this.persistenceListener = listener;
+    }
+
+    /**
+     * Get the persistence listener.
+     */
+    public PersistenceListener getPersistenceListener() {
+        return persistenceListener;
+    }
+
+    /**
+     * Sets the unlimited disk caching flag.
+     */
+    public void setUnlimitedDiskCache(boolean unlimitedDiskCache) {
+        this.unlimitedDiskCache = unlimitedDiskCache;
+    }
+
+    /**
+     * Check if we use unlimited disk cache.
+     */
+    public boolean isUnlimitedDiskCache() {
+        return unlimitedDiskCache;
+    }
+
+    /**
+     * Check if we use overflowPersistence
+     *
+     * @return Returns the overflowPersistence.
+     */
+    public boolean isOverflowPersistence() {
+        return this.overflowPersistence;
+    }
+
+    /**
+     * Sets the overflowPersistence flag
+     *
+     * @param overflowPersistence The overflowPersistence to set.
+     */
+    public void setOverflowPersistence(boolean overflowPersistence) {
+        this.overflowPersistence = overflowPersistence;
+    }
+
+    /**
+     * Return the number of slots in this table.
+     **/
+    public synchronized int capacity() {
+        return table.length;
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public synchronized void clear() {
+        Entry[] tab = table;
+
+        for (int i = 0; i < tab.length; ++i) {
+            // must invalidate all to force concurrent get's to wait and then retry
+            for (Entry e = tab[i]; e != null; e = e.next) {
+                e.value = null;
+
+                /** OpenSymphony BEGIN */
+                itemRemoved(e.key);
+
+                /** OpenSymphony END */
+            }
+
+            tab[i] = null;
+        }
+
+        // Clean out the entire disk cache
+        persistClear();
+
+        count = 0;
+        recordModification(tab);
+    }
+
+    /**
+     * Returns a shallow copy of this.
+     * <tt>AbstractConcurrentReadCache</tt> instance: the keys and
+     * values themselves are not cloned.
+     *
+     * @return a shallow copy of this map.
+     */
+    public synchronized Object clone() {
+        try {
+            AbstractConcurrentReadCache t = (AbstractConcurrentReadCache) super.clone();
+            t.keySet = null;
+            t.entrySet = null;
+            t.values = null;
+
+            Entry[] tab = table;
+            t.table = new Entry[tab.length];
+
+            Entry[] ttab = t.table;
+
+            for (int i = 0; i < tab.length; ++i) {
+                Entry first = tab[i];
+
+                if (first != null) {
+                    ttab[i] = (Entry) (first.clone());
+                }
+            }
+
+            return t;
+        } catch (CloneNotSupportedException e) {
+            // this shouldn't happen, since we are Cloneable
+            throw new InternalError();
+        }
+    }
+
+    /**
+     * Tests if some key maps into the specified value in this table.
+     * This operation is more expensive than the <code>containsKey</code>
+     * method.<p>
+     *
+     * Note that this method is identical in functionality to containsValue,
+     * (which is part of the Map interface in the collections framework).
+     *
+     * @param      value   a value to search for.
+     * @return     <code>true</code> if and only if some key maps to the
+     *             <code>value</code> argument in this table as
+     *             determined by the <tt>equals</tt> method;
+     *             <code>false</code> otherwise.
+     * @exception  NullPointerException  if the value is <code>null</code>.
+     * @see        #containsKey(Object)
+     * @see        #containsValue(Object)
+     * @see           Map
+     */
+    public boolean contains(Object value) {
+        return containsValue(value);
+    }
+
+    /**
+     * Tests if the specified object is a key in this table.
+     *
+     * @param   key   possible key.
+     * @return  <code>true</code> if and only if the specified object
+     *          is a key in this table, as determined by the
+     *          <tt>equals</tt> method; <code>false</code> otherwise.
+     * @exception  NullPointerException  if the key is
+     *               <code>null</code>.
+     * @see     #contains(Object)
+     */
+    public boolean containsKey(Object key) {
+        return get(key) != null;
+
+        /** OpenSymphony BEGIN */
+
+        // TODO: Also check the persistence?
+
+        /** OpenSymphony END */
+    }
+
+    /**
+     * Returns <tt>true</tt> if this map maps one or more keys to the
+     * specified value. Note: This method requires a full internal
+     * traversal of the hash table, and so is much slower than
+     * method <tt>containsKey</tt>.
+     *
+     * @param value value whose presence in this map is to be tested.
+     * @return <tt>true</tt> if this map maps one or more keys to the
+     * specified value.
+     * @exception  NullPointerException  if the value is <code>null</code>.
+     */
+    public boolean containsValue(Object value) {
+        if (value == null) {
+            throw new NullPointerException();
+        }
+
+        Entry[] tab = getTableForReading();
+
+        for (int i = 0; i < tab.length; ++i) {
+            for (Entry e = tab[i]; e != null; e = e.next) {
+                Object v = e.value;
+
+                if ((v != null) && value.equals(v)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns an enumeration of the values in this table.
+     * Use the Enumeration methods on the returned object to fetch the elements
+     * sequentially.
+     *
+     * @return  an enumeration of the values in this table.
+     * @see     java.util.Enumeration
+     * @see     #keys()
+     * @see        #values()
+     * @see        Map
+     */
+    public Enumeration elements() {
+        return new ValueIterator();
+    }
+
+    /**
+     * Returns a collection view of the mappings contained in this map.
+     * Each element in the returned collection is a <tt>Map.Entry</tt>.  The
+     * collection is backed by the map, so changes to the map are reflected in
+     * the collection, and vice-versa.  The collection supports element
+     * removal, which removes the corresponding mapping from the map, via the
+     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
+     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
+     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
+     *
+     * @return a collection view of the mappings contained in this map.
+     */
+    public Set entrySet() {
+        Set es = entrySet;
+
+        if (es != null) {
+            return es;
+        } else {
+            return entrySet = new AbstractSet() {
+                        public Iterator iterator() {
+                            return new HashIterator();
+                        }
+
+                        public boolean contains(Object o) {
+                            if (!(o instanceof Map.Entry)) {
+                                return false;
+                            }
+
+                            Map.Entry entry = (Map.Entry) o;
+                            Object key = entry.getKey();
+                            Object v = AbstractConcurrentReadCache.this.get(key);
+
+                            return (v != null) && v.equals(entry.getValue());
+                        }
+
+                        public boolean remove(Object o) {
+                            if (!(o instanceof Map.Entry)) {
+                                return false;
+                            }
+
+                            return AbstractConcurrentReadCache.this.findAndRemoveEntry((Map.Entry) o);
+                        }
+
+                        public int size() {
+                            return AbstractConcurrentReadCache.this.size();
+                        }
+
+                        public void clear() {
+                            AbstractConcurrentReadCache.this.clear();
+                        }
+                    };
+        }
+    }
+
+    /**
+     * Returns the value to which the specified key is mapped in this table.
+     *
+     * @param   key   a key in the table.
+     * @return  the value to which the key is mapped in this table;
+     *          <code>null</code> if the key is not mapped to any value in
+     *          this table.
+     * @exception  NullPointerException  if the key is
+     *               <code>null</code>.
+     * @see     #put(Object, Object)
+     */
+    public Object get(Object key) {
+        if (log.isDebugEnabled()) {
+            log.debug("get called (key=" + key + ")");
+        }
+
+        // throw null pointer exception if key null
+        int hash = hash(key);
+
+        /*
+           Start off at the apparently correct bin.  If entry is found, we
+           need to check after a barrier anyway.  If not found, we need a
+           barrier to check if we are actually in right bin. So either
+           way, we encounter only one barrier unless we need to retry.
+           And we only need to fully synchronize if there have been
+           concurrent modifications.
+        */
+        Entry[] tab = table;
+        int index = hash & (tab.length - 1);
+        Entry first = tab[index];
+        Entry e = first;
+
+        for (;;) {
+            if (e == null) {
+                // If key apparently not there, check to
+                // make sure this was a valid read
+                tab = getTableForReading();
+
+                if (first == tab[index]) {
+                    /** OpenSymphony BEGIN */
+
+                    /* Previous code
+                    return null;*/
+
+                    // Not in the table, try persistence
+                    Object value = persistRetrieve(key);
+
+                    if (value != null) {
+                        // Update the map, but don't persist the data
+                        put(key, value, false);
+                    }
+
+                    return value;
+
+                    /** OpenSymphony END */
+                } else {
+                    // Wrong list -- must restart traversal at new first
+                    e = first = tab[index = hash & (tab.length - 1)];
+                }
+            }
+            // checking for pointer equality first wins in most applications
+            else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
+                Object value = e.value;
+
+                if (value != null) {
+                    /** OpenSymphony BEGIN */
+
+                    /* Previous code
+                    return value;*/
+                    if (NULL.equals(value)) {
+                        // Memory cache disable, use disk
+                        value = persistRetrieve(e.key);
+
+                        if (value != null) {
+                            itemRetrieved(key);
+                        }
+
+                        return value; // fix [CACHE-13]
+                    } else {
+                        itemRetrieved(key);
+
+                        return value;
+                    }
+
+                    /** OpenSymphony END */
+                }
+
+                // Entry was invalidated during deletion. But it could
+                // have been re-inserted, so we must retraverse.
+                // To avoid useless contention, get lock to wait out modifications
+                // before retraversing.
+                synchronized (this) {
+                    tab = table;
+                }
+
+                e = first = tab[index = hash & (tab.length - 1)];
+            } else {
+                e = e.next;
+            }
+        }
+    }
+
+    /**
+     * Returns a set view of the keys contained in this map.
+     * The set is backed by the map, so changes to the map are reflected in the set, and
+     * vice-versa.  The set supports element removal, which removes the
+     * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
+     * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
+     * <tt>clear</tt> operations.  It does not support the <tt>add</tt> or
+     * <tt>addAll</tt> operations.
+     *
+     * @return a set view of the keys contained in this map.
+     */
+    public Set keySet() {
+        Set ks = keySet;
+
+        if (ks != null) {
+            return ks;
+        } else {
+            return keySet = new AbstractSet() {
+                        public Iterator iterator() {
+                            return new KeyIterator();
+                        }
+
+                        public int size() {
+                            return AbstractConcurrentReadCache.this.size();
+                        }
+
+                        public boolean contains(Object o) {
+                            return AbstractConcurrentReadCache.this.containsKey(o);
+                        }
+
+                        public boolean remove(Object o) {
+                            return AbstractConcurrentReadCache.this.remove(o) != null;
+                        }
+
+                        public void clear() {
+                            AbstractConcurrentReadCache.this.clear();
+                        }
+                    };
+        }
+    }
+
+    /**
+     * Returns an enumeration of the keys in this table.
+     *
+     * @return  an enumeration of the keys in this table.
+     * @see     Enumeration
+     * @see     #elements()
+     * @see        #keySet()
+     * @see        Map
+     */
+    public Enumeration keys() {
+        return new KeyIterator();
+    }
+
+    /**
+     * Return the load factor
+     **/
+    public float loadFactor() {
+        return loadFactor;
+    }
+
+    /**
+     * Maps the specified <code>key</code> to the specified <code>value</code> in this table.
+     * Neither the key nor the
+     * value can be <code>null</code>. <p>
+     *
+     * The value can be retrieved by calling the <code>get</code> method
+     * with a key that is equal to the original key.
+     *
+     * @param      key     the table key.
+     * @param      value   the value.
+     * @return     the previous value of the specified key in this table,
+     *             or <code>null</code> if it did not have one.
+     * @exception  NullPointerException  if the key or value is
+     *               <code>null</code>.
+     * @see     Object#equals(Object)
+     * @see     #get(Object)
+     */
+    /** OpenSymphony BEGIN */
+    public Object put(Object key, Object value) {
+        // Call the internal put using persistance
+        return put(key, value, true);
+    }
+
+    /**
+     * Copies all of the mappings from the specified map to this one.
+     *
+     * These mappings replace any mappings that this map had for any of the
+     * keys currently in the specified Map.
+     *
+     * @param t Mappings to be stored in this map.
+     */
+    public synchronized void putAll(Map t) {
+        for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            put(key, value);
+        }
+    }
+
+    /**
+     * Removes the key (and its corresponding value) from this table.
+     * This method does nothing if the key is not in the table.
+     *
+     * @param   key   the key that needs to be removed.
+     * @return  the value to which the key had been mapped in this table,
+     *          or <code>null</code> if the key did not have a mapping.
+     */
+    /** OpenSymphony BEGIN */
+    public Object remove(Object key) {
+        return remove(key, true, false);
+    }
+
+    /**
+     * Like <code>remove(Object)</code>, but ensures that the entry will be removed from the persistent store, too,
+     * even if overflowPersistence or unlimitedDiskcache are true.
+     *
+     * @param   key   the key that needs to be removed.
+     * @return  the value to which the key had been mapped in this table,
+     *          or <code>null</code> if the key did not have a mapping.
+     */
+    public Object removeForce(Object key) {
+      return remove(key, true, true);
+    }
+
+    /**
+     * Returns the total number of cache entries held in this map.
+     *
+     * @return the number of key-value mappings in this map.
+     */
+    public synchronized int size() {
+        return count;
+    }
+
+    /**
+     * Returns a collection view of the values contained in this map.
+     * The collection is backed by the map, so changes to the map are reflected in
+     * the collection, and vice-versa.  The collection supports element
+     * removal, which removes the corresponding mapping from this map, via the
+     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
+     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
+     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
+     *
+     * @return a collection view of the values contained in this map.
+     */
+    public Collection values() {
+        Collection vs = values;
+
+        if (vs != null) {
+            return vs;
+        } else {
+            return values = new AbstractCollection() {
+                        public Iterator iterator() {
+                            return new ValueIterator();
+                        }
+
+                        public int size() {
+                            return AbstractConcurrentReadCache.this.size();
+                        }
+
+                        public boolean contains(Object o) {
+                            return AbstractConcurrentReadCache.this.containsValue(o);
+                        }
+
+                        public void clear() {
+                            AbstractConcurrentReadCache.this.clear();
+                        }
+                    };
+        }
+    }
+
+    /**
+     * Get ref to group.
+     * CACHE-127 Synchronized copying of the group entry set since
+     * the new HashSet(Collection c) constructor uses the iterator.
+     * This may slow things down but it is better than a
+     * ConcurrentModificationException.  We might have to revisit the
+     * code if performance is too adversely impacted.
+     **/
+    protected synchronized final Set getGroupForReading(String groupName) {
+        Set group = (Set) getGroupsForReading().get(groupName);
+        if (group == null) return null;
+        return new HashSet(group);
+    }
+
+    /**
+     * Get ref to groups.
+     * The reference and the cells it
+     * accesses will be at least as fresh as from last
+     * use of barrierLock
+     **/
+    protected final Map getGroupsForReading() {
+        synchronized (barrierLock) {
+            return groups;
+        }
+    }
+
+    /**
+     * Get ref to table; the reference and the cells it
+     * accesses will be at least as fresh as from last
+     * use of barrierLock
+     **/
+    protected final Entry[] getTableForReading() {
+        synchronized (barrierLock) {
+            return table;
+        }
+    }
+
+    /**
+     * Force a memory synchronization that will cause
+     * all readers to see table. Call only when already
+     * holding main synch lock.
+     **/
+    protected final void recordModification(Object x) {
+        synchronized (barrierLock) {
+            lastWrite = x;
+        }
+    }
+
+    /**
+     * Helper method for entrySet remove.
+     **/
+    protected synchronized boolean findAndRemoveEntry(Map.Entry entry) {
+        Object key = entry.getKey();
+        Object v = get(key);
+
+        if ((v != null) && v.equals(entry.getValue())) {
+            remove(key);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Remove an object from the persistence.
+     * @param key The key of the object to remove
+     */
+    protected void persistRemove(Object key) {
+        if (log.isDebugEnabled()) {
+            log.debug("PersistRemove called (key=" + key + ")");
+        }
+
+        if (persistenceListener != null) {
+            try {
+                persistenceListener.remove((String) key);
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception removing cache entry with key '" + key + "' from persistence", e);
+            }
+        }
+    }
+
+    /**
+     * Removes a cache group using the persistence listener.
+     * @param groupName The name of the group to remove
+     */
+    protected void persistRemoveGroup(String groupName) {
+        if (log.isDebugEnabled()) {
+            log.debug("persistRemoveGroup called (groupName=" + groupName + ")");
+        }
+
+        if (persistenceListener != null) {
+            try {
+                persistenceListener.removeGroup(groupName);
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception removing group " + groupName, e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve an object from the persistence listener.
+     * @param key The key of the object to retrieve
+     */
+    protected Object persistRetrieve(Object key) {
+        if (log.isDebugEnabled()) {
+            log.debug("persistRetrieve called (key=" + key + ")");
+        }
+
+        Object entry = null;
+
+        if (persistenceListener != null) {
+            try {
+                entry = persistenceListener.retrieve((String) key);
+            } catch (CachePersistenceException e) {
+                /**
+                 * It is normal that we get an exception occasionally.
+                 * It happens when the item is invalidated (written or removed)
+                 * during read. The logic is constructed so that read is retried.
+                 */
+            }
+        }
+
+        return entry;
+    }
+
+    /**
+     * Retrieves a cache group using the persistence listener.
+     * @param groupName The name of the group to retrieve
+     */
+    protected Set persistRetrieveGroup(String groupName) {
+        if (log.isDebugEnabled()) {
+            log.debug("persistRetrieveGroup called (groupName=" + groupName + ")");
+        }
+
+        if (persistenceListener != null) {
+            try {
+                return persistenceListener.retrieveGroup(groupName);
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception retrieving group " + groupName, e);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Store an object in the cache using the persistence listener.
+     * @param key The object key
+     * @param obj The object to store
+     */
+    protected void persistStore(Object key, Object obj) {
+        if (log.isDebugEnabled()) {
+            log.debug("persistStore called (key=" + key + ")");
+        }
+
+        if (persistenceListener != null) {
+            try {
+                persistenceListener.store((String) key, obj);
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception persisting " + key, e);
+            }
+        }
+    }
+
+    /**
+     * Creates or Updates a cache group using the persistence listener.
+     * @param groupName The name of the group to update
+     * @param group The entries for the group
+     */
+    protected void persistStoreGroup(String groupName, Set group) {
+        if (log.isDebugEnabled()) {
+            log.debug("persistStoreGroup called (groupName=" + groupName + ")");
+        }
+
+        if (persistenceListener != null) {
+            try {
+                if ((group == null) || group.isEmpty()) {
+                    persistenceListener.removeGroup(groupName);
+                } else {
+                    persistenceListener.storeGroup(groupName, group);
+                }
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception persisting group " + groupName, e);
+            }
+        }
+    }
+
+    /**
+     * Removes the entire cache from persistent storage.
+     */
+    protected void persistClear() {
+        if (log.isDebugEnabled()) {
+            log.debug("persistClear called");
+            ;
+        }
+
+        if (persistenceListener != null) {
+            try {
+                persistenceListener.clear();
+            } catch (CachePersistenceException e) {
+                log.error("[oscache] Exception clearing persistent cache", e);
+            }
+        }
+    }
+
+    /**
+     * Notify the underlying implementation that an item was put in the cache.
+     *
+     * @param key The cache key of the item that was put.
+     */
+    protected abstract void itemPut(Object key);
+
+    /**
+     * Notify any underlying algorithm that an item has been retrieved from the cache.
+     *
+     * @param key The cache key of the item that was retrieved.
+     */
+    protected abstract void itemRetrieved(Object key);
+
+    /**
+     * Notify the underlying implementation that an item was removed from the cache.
+     *
+     * @param key The cache key of the item that was removed.
+     */
+    protected abstract void itemRemoved(Object key);
+
+    /**
+     * The cache has reached its cacpacity and an item needs to be removed.
+     * (typically according to an algorithm such as LRU or FIFO).
+     *
+     * @return The key of whichever item was removed.
+     */
+    protected abstract Object removeItem();
+
+    /**
+     * Reconstitute the <tt>AbstractConcurrentReadCache</tt>.
+     * instance from a stream (i.e.,
+     * deserialize it).
+     */
+    private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+        // Read in the threshold, loadfactor, and any hidden stuff
+        s.defaultReadObject();
+
+        // Read in number of buckets and allocate the bucket array;
+        int numBuckets = s.readInt();
+        table = new Entry[numBuckets];
+
+        // Read in size (number of Mappings)
+        int size = s.readInt();
+
+        // Read the keys and values, and put the mappings in the table
+        for (int i = 0; i < size; i++) {
+            Object key = s.readObject();
+            Object value = s.readObject();
+            put(key, value);
+        }
+    }
+
+    /**
+     * Rehashes the contents of this map into a new table with a larger capacity.
+     * This method is called automatically when the
+     * number of keys in this map exceeds its capacity and load factor.
+     */
+    protected void rehash() {
+        Entry[] oldMap = table;
+        int oldCapacity = oldMap.length;
+
+        if (oldCapacity >= MAXIMUM_CAPACITY) {
+            return;
+        }
+
+        int newCapacity = oldCapacity << 1;
+        Entry[] newMap = new Entry[newCapacity];
+        threshold = (int) (newCapacity * loadFactor);
+
+        /*
+          We need to guarantee that any existing reads of oldMap can
+          proceed. So we cannot yet null out each oldMap bin.
+
+          Because we are using power-of-two expansion, the elements
+          from each bin must either stay at same index, or move
+          to oldCapacity+index. We also minimize new node creation by
+          catching cases where old nodes can be reused because their
+          .next fields won't change. (This is checked only for sequences
+          of one and two. It is not worth checking longer ones.)
+        */
+        for (int i = 0; i < oldCapacity; ++i) {
+            Entry l = null;
+            Entry h = null;
+            Entry e = oldMap[i];
+
+            while (e != null) {
+                int hash = e.hash;
+                Entry next = e.next;
+
+                if ((hash & oldCapacity) == 0) {
+                    // stays at newMap[i]
+                    if (l == null) {
+                        // try to reuse node
+                        if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) == 0))) {
+                            l = e;
+
+                            break;
+                        }
+                    }
+
+                    l = new Entry(hash, e.key, e.value, l);
+                } else {
+                    // moves to newMap[oldCapacity+i]
+                    if (h == null) {
+                        if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) != 0))) {
+                            h = e;
+
+                            break;
+                        }
+                    }
+
+                    h = new Entry(hash, e.key, e.value, h);
+                }
+
+                e = next;
+            }
+
+            newMap[i] = l;
+            newMap[oldCapacity + i] = h;
+        }
+
+        table = newMap;
+        recordModification(newMap);
+    }
+
+    /**
+     * Continuation of put(), called only when synch lock is
+     * held and interference has been detected.
+     **/
+    /** OpenSymphony BEGIN */
+
+    /* Previous code
+    protected Object sput(Object key, Object value, int hash) {*/
+    protected Object sput(Object key, Object value, int hash, boolean persist) {
+        /** OpenSymphony END */
+        Entry[] tab = table;
+        int index = hash & (tab.length - 1);
+        Entry first = tab[index];
+        Entry e = first;
+
+        for (;;) {
+            if (e == null) {
+                /** OpenSymphony BEGIN */
+
+                // Previous code
+                //  		Entry newEntry = new Entry(hash, key, value, first);
+                Entry newEntry;
+
+                if (memoryCaching) {
+                    newEntry = new Entry(hash, key, value, first);
+                } else {
+                    newEntry = new Entry(hash, key, NULL, first);
+                }
+
+                itemPut(key);
+
+                // Persist if required
+                if (persist && !overflowPersistence) {
+                    persistStore(key, value);
+                }
+
+                // If we have a CacheEntry, update the group lookups
+                if (value instanceof CacheEntry) {
+                    updateGroups(null, (CacheEntry) value, persist);
+                }
+
+                /**        OpenSymphony END */
+                tab[index] = newEntry;
+
+                if (++count >= threshold) {
+                    rehash();
+                } else {
+                    recordModification(newEntry);
+                }
+
+                return null;
+            } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
+                Object oldValue = e.value;
+
+                /** OpenSymphony BEGIN */
+
+                /* Previous code
+                e.value = value; */
+                if (memoryCaching) {
+                    e.value = value;
+                }
+
+                // Persist if required
+                if (persist && overflowPersistence) {
+                    persistRemove(key);
+                } else if (persist) {
+                    persistStore(key, value);
+                }
+
+                updateGroups(oldValue, value, persist);
+
+                itemPut(key);
+
+                /** OpenSymphony END */
+                return oldValue;
+            } else {
+                e = e.next;
+            }
+        }
+    }
+
+    /**
+     * Continuation of remove(), called only when synch lock is
+     * held and interference has been detected.
+     **/
+    /** OpenSymphony BEGIN */
+
+    /* Previous code
+    protected Object sremove(Object key, int hash) { */
+    protected Object sremove(Object key, int hash, boolean invokeAlgorithm) {
+        /** OpenSymphony END */
+        Entry[] tab = table;
+        int index = hash & (tab.length - 1);
+        Entry first = tab[index];
+        Entry e = first;
+
+        for (;;) {
+            if (e == null) {
+                return null;
+            } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
+                Object oldValue = e.value;
+                if (persistenceListener != null && (oldValue == NULL)) {
+                  oldValue = persistRetrieve(key);
+                }
+
+                e.value = null;
+                count--;
+
+                /** OpenSymphony BEGIN */
+                if (!unlimitedDiskCache && !overflowPersistence) {
+                    persistRemove(e.key);
+                    // If we have a CacheEntry, update the groups
+                    if (oldValue instanceof CacheEntry) {
+                      CacheEntry oldEntry = (CacheEntry)oldValue;
+                      removeGroupMappings(oldEntry.getKey(),
+                          oldEntry.getGroups(), true);
+                }
+                } else {
+                  // only remove from memory groups
+                  if (oldValue instanceof CacheEntry) {
+                    CacheEntry oldEntry = (CacheEntry)oldValue;
+                    removeGroupMappings(oldEntry.getKey(),
+                        oldEntry.getGroups(), false);
+                  }
+                }
+
+                if (overflowPersistence && ((size() + 1) >= maxEntries)) {
+                    persistStore(key, oldValue);
+                    // add key to persistent groups but NOT to the memory groups
+                    if (oldValue instanceof CacheEntry) {
+                      CacheEntry oldEntry = (CacheEntry)oldValue;
+                      addGroupMappings(oldEntry.getKey(), oldEntry.getGroups(), true, false);
+                    }
+                }
+
+                if (invokeAlgorithm) {
+                    itemRemoved(key);
+                }
+
+                /** OpenSymphony END */
+                Entry head = e.next;
+
+                for (Entry p = first; p != e; p = p.next) {
+                    head = new Entry(p.hash, p.key, p.value, head);
+                }
+
+                tab[index] = head;
+                recordModification(head);
+
+                return oldValue;
+            } else {
+                e = e.next;
+            }
+        }
+    }
+
+    /**
+     * Save the state of the <tt>AbstractConcurrentReadCache</tt> instance to a stream.
+     * (i.e., serialize it).
+     *
+     * @serialData The <i>capacity</i> of the
+     * AbstractConcurrentReadCache (the length of the
+     * bucket array) is emitted (int), followed  by the
+     * <i>size</i> of the AbstractConcurrentReadCache (the number of key-value
+     * mappings), followed by the key (Object) and value (Object)
+     * for each key-value mapping represented by the AbstractConcurrentReadCache
+     * The key-value mappings are emitted in no particular order.
+     */
+    private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {
+        // Write out the threshold, loadfactor, and any hidden stuff
+        s.defaultWriteObject();
+
+        // Write out number of buckets
+        s.writeInt(table.length);
+
+        // Write out size (number of Mappings)
+        s.writeInt(count);
+
+        // Write out keys and values (alternating)
+        for (int index = table.length - 1; index >= 0; index--) {
+            Entry entry = table[index];
+
+            while (entry != null) {
+                s.writeObject(entry.key);
+                s.writeObject(entry.value);
+                entry = entry.next;
+            }
+        }
+    }
+
+    /**
+     * Return hash code for Object x.
+     * Since we are using power-of-two
+     * tables, it is worth the effort to improve hashcode via
+     * the same multiplicative scheme as used in IdentityHashMap.
+     */
+    private static int hash(Object x) {
+        int h = x.hashCode();
+
+        // Multiply by 127 (quickly, via shifts), and mix in some high
+        // bits to help guard against bunching of codes that are
+        // consecutive or equally spaced.
+        return ((h << 7) - h + (h >>> 9) + (h >>> 17));
+    }
+
+    /**
+     * 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.
+     * @param memory A flag to indicate whether the key should be added to
+     * the memory groups (important for overflow-to-disk)
+     */
+    private void addGroupMappings(String key, Set newGroups, boolean persist, boolean memory) {
+        if (newGroups == null) {
+            return;
+        }
+        
+        // Add this CacheEntry to the groups that it is now a member of
+        for (Iterator it = newGroups.iterator(); it.hasNext();) {
+            String groupName = (String) it.next();
+
+            // Update the in-memory groups
+            if (memoryCaching && memory) {
+                if (groups == null) {
+                    groups = new HashMap();
+                }
+
+                Set memoryGroup = (Set) groups.get(groupName);
+
+                if (memoryGroup == null) {
+                    memoryGroup = new HashSet();
+                    groups.put(groupName, memoryGroup);
+                }
+
+                memoryGroup.add(key);
+            }
+
+            // Update the persistent group maps
+            if (persist) {
+                Set persistentGroup = persistRetrieveGroup(groupName);
+
+                if (persistentGroup == null) {
+                    persistentGroup = new HashSet();
+                }
+
+                persistentGroup.add(key);
+                persistStoreGroup(groupName, persistentGroup);
+            }
+        }
+    }
+
+    /** OpenSymphony END (pretty long!) */
+    /**
+     * Returns the appropriate capacity (power of two) for the specified
+     * initial capacity argument.
+     */
+    private int p2capacity(int initialCapacity) {
+        int cap = initialCapacity;
+
+        // Compute the appropriate capacity
+        int result;
+
+        if ((cap > MAXIMUM_CAPACITY) || (cap < 0)) {
+            result = MAXIMUM_CAPACITY;
+        } else {
+            result = MINIMUM_CAPACITY;
+
+            while (result < cap) {
+                result <<= 1;
+            }
+        }
+
+        return result;
+    }
+
+    /* Previous code
+    public Object put(Object key, Object value)*/
+    private Object put(Object key, Object value, boolean persist) {
+        /** OpenSymphony END */
+        if (value == null) {
+            throw new NullPointerException();
+        }
+
+        int hash = hash(key);
+        Entry[] tab = table;
+        int index = hash & (tab.length - 1);
+        Entry first = tab[index];
+        Entry e = first;
+
+        for (;;) {
+            if (e == null) {
+                synchronized (this) {
+                    tab = table;
+
+                    /** OpenSymphony BEGIN */
+
+                    // Previous code
+
+                    /*                                        if (first == tab[index]) {
+                                                                    //  Add to front of list
+                                                                    Entry newEntry = new Entry(hash, key, value, first);
+                                                                    tab[index] = newEntry;
+                                                                    if (++count >= threshold) rehash();
+                                                                    else recordModification(newEntry);
+                                                                    return null; */
+
+                    Object oldValue = null;
+
+                    // Remove an item if the cache is full
+                    if (size() >= maxEntries) {
+                        // part of fix CACHE-255: method should return old value
+                        oldValue = remove(removeItem(), false, false);
+                    }
+
+                    if (first == tab[index]) {
+                        //  Add to front of list
+                        Entry newEntry = null;
+
+                        if (memoryCaching) {
+                            newEntry = new Entry(hash, key, value, first);
+                        } else {
+                            newEntry = new Entry(hash, key, NULL, first);
+                        }
+
+                        tab[index] = newEntry;
+                        itemPut(key);
+
+                        // Persist if required
+                        if (persist && !overflowPersistence) {
+                            persistStore(key, value);
+                        }
+
+                        // If we have a CacheEntry, update the group lookups
+                        if (value instanceof CacheEntry) {
+                            updateGroups(null, (CacheEntry) value, persist);
+                        }
+
+                        if (++count >= threshold) {
+                            rehash();
+                        } else {
+                            recordModification(newEntry);
+                        }
+                        
+                        return oldValue;
+
+                        /** OpenSymphony END  */
+                    } else {
+                        // wrong list -- retry
+
+                        /** OpenSymphony BEGIN */
+
+                        /* Previous code
+                        return sput(key, value, hash);*/
+                        return sput(key, value, hash, persist);
+
+                        /** OpenSymphony END */
+                    }
+                }
+            } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
+                // synch to avoid race with remove and to
+                // ensure proper serialization of multiple replaces
+                synchronized (this) {
+                    tab = table;
+
+                    Object oldValue = e.value;
+
+                    // [CACHE-118] - get the old cache entry even if there's no memory cache
+                    if (persist && (oldValue == NULL)) {
+                        oldValue = persistRetrieve(key);
+                    }
+
+                    if ((first == tab[index]) && (oldValue != null)) {
+                        /** OpenSymphony BEGIN */
+
+                        /* Previous code
+                        e.value = value;
+                        return oldValue; */
+                        if (memoryCaching) {
+                            e.value = value;
+                        }
+
+                        // Persist if required
+                        if (persist && overflowPersistence) {
+                            persistRemove(key);
+                        } else if (persist) {
+                            persistStore(key, value);
+                        }
+
+                        updateGroups(oldValue, value, persist);
+                        itemPut(key);
+
+                        return oldValue;
+
+                        /**        OpenSymphony END */
+                    } else {
+                        // retry if wrong list or lost race against concurrent remove
+
+                        /** OpenSymphony BEGIN */
+
+                        /* Previous code
+                        return sput(key, value, hash);*/
+                        return sput(key, value, hash, persist);
+
+                        /** OpenSymphony END */
+                    }
+                }
+            } else {
+                e = e.next;
+            }
+        }
+    }
+
+    private synchronized Object remove(Object key, boolean invokeAlgorithm, boolean forcePersist)
+    /* Previous code
+    public Object remove(Object key) */
+
+    /** OpenSymphony END */  {
+        /*
+          Strategy:
+
+          Find the entry, then
+            1. Set value field to null, to force get() to retry
+            2. Rebuild the list without this entry.
+               All entries following removed node can stay in list, but
+               all preceeding ones need to be cloned.  Traversals rely
+               on this strategy to ensure that elements will not be
+              repeated during iteration.
+        */
+
+        /** OpenSymphony BEGIN */
+        if (key == null) {
+            return null;
+        }
+
+        /** OpenSymphony END */
+        int hash = hash(key);
+        Entry[] tab = table;
+        int index = hash & (tab.length - 1);
+        Entry first = tab[index];
+        Entry e = first;
+
+        for (;;) {
+            if (e == null) {
+                tab = getTableForReading();
+
+                if (first == tab[index]) {
+                    return null;
+                } else {
+                    // Wrong list -- must restart traversal at new first
+
+                    /** OpenSymphony BEGIN */
+
+                    /* Previous Code
+                    return sremove(key, hash); */
+                    return sremove(key, hash, invokeAlgorithm);
+
+                    /** OpenSymphony END */
+                }
+            } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
+                synchronized (this) {
+                    tab = table;
+
+                    Object oldValue = e.value;
+                    if (persistenceListener != null && (oldValue == NULL)) {
+                      oldValue = persistRetrieve(key);
+                    }
+
+                    // re-find under synch if wrong list
+                    if ((first != tab[index]) || (oldValue == null)) {
+                        /** OpenSymphony BEGIN */
+
+                        /* Previous Code
+                        return sremove(key, hash); */
+                        return sremove(key, hash, invokeAlgorithm);
+                    }
+
+                    /** OpenSymphony END */
+                    e.value = null;
+                    count--;
+
+                    /** OpenSymphony BEGIN */
+                    if (forcePersist || (!unlimitedDiskCache && !overflowPersistence)) {
+                        persistRemove(e.key);
+                        // If we have a CacheEntry, update the group lookups
+                        if (oldValue instanceof CacheEntry) {
+                          CacheEntry oldEntry = (CacheEntry) oldValue;
+                            removeGroupMappings(oldEntry.getKey(),
+                                oldEntry.getGroups(), true);
+                        }
+                    } else {
+						// only remove from memory groups
+						if (oldValue instanceof CacheEntry) {
+							CacheEntry oldEntry = (CacheEntry) oldValue;
+							removeGroupMappings(oldEntry.getKey(), oldEntry
+									.getGroups(), false);
+						}
+                    }
+
+                    if (!forcePersist && overflowPersistence && ((size() + 1) >= maxEntries)) {
+                        persistStore(key, oldValue);
+                        // add key to persistent groups but NOT to the memory groups
+                        if (oldValue instanceof CacheEntry) {
+                        	CacheEntry oldEntry = (CacheEntry) oldValue;
+                        	addGroupMappings(oldEntry.getKey(), oldEntry.getGroups(), true, false);
+                        }
+                    }
+
+                    if (invokeAlgorithm) {
+                        itemRemoved(key);
+                    }
+
+                    // introduced to fix bug CACHE-255 
+                    if (oldValue instanceof CacheEntry) {
+                    	CacheEntry oldEntry = (CacheEntry) oldValue;
+                    	oldValue = oldEntry.getContent();
+                    }
+
+                    /** OpenSymphony END */
+                    Entry head = e.next;
+
+                    for (Entry p = first; p != e; p = p.next) {
+                        head = new Entry(p.hash, p.key, p.value, head);
+                    }
+
+                    tab[index] = head;
+                    recordModification(head);
+
+                    return oldValue;
+                }
+            } else {
+                e = e.next;
+            }
+        }
+    }
+
+    /**
+     * Remove this CacheEntry from the groups it no longer belongs to.
+     * We have to treat the memory and disk group mappings separately 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 removing from the groups.
+     * @param oldGroups the set of groups we want to remove the cache entry
+     * from.
+     * @param persist A flag to indicate whether the keys should be removed
+     * from the persistent cache layer.
+     */
+    private void removeGroupMappings(String key, Set oldGroups, boolean persist) {
+        if (oldGroups == null) {
+          return;
+        }
+
+        for (Iterator it = oldGroups.iterator(); it.hasNext();) {
+            String groupName = (String) it.next();
+
+            // Update the in-memory groups
+            if (memoryCaching && (this.groups != null)) {
+                Set memoryGroup = (Set) groups.get(groupName);
+
+                if (memoryGroup != null) {
+                    memoryGroup.remove(key);
+
+                    if (memoryGroup.isEmpty()) {
+                        groups.remove(groupName);
+                    }
+                }
+            }
+
+            // Update the persistent group maps
+            if (persist) {
+                Set persistentGroup = persistRetrieveGroup(groupName);
+
+                if (persistentGroup != null) {
+                    persistentGroup.remove(key);
+
+                    if (persistentGroup.isEmpty()) {
+                        persistRemoveGroup(groupName);
+                    } else {
+                        persistStoreGroup(groupName, persistentGroup);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates the groups to reflect the differences between the old and new
+     * cache entries. Either of the old or new values can be <code>null</code>
+     * or contain a <code>null</code> group list, in which case the entry's
+     * groups will all be added or removed respectively.
+     *
+     * @param oldValue The old CacheEntry that is being replaced.
+     * @param newValue The new CacheEntry that is being inserted.
+     */
+    private void updateGroups(Object oldValue, Object newValue, boolean persist) {
+        // If we have/had a CacheEntry, update the group lookups
+        boolean oldIsCE = oldValue instanceof CacheEntry;
+        boolean newIsCE = newValue instanceof CacheEntry;
+
+        if (newIsCE && oldIsCE) {
+            updateGroups((CacheEntry) oldValue, (CacheEntry) newValue, persist);
+        } else if (newIsCE) {
+            updateGroups(null, (CacheEntry) newValue, persist);
+        } else if (oldIsCE) {
+            updateGroups((CacheEntry) oldValue, null, persist);
+        }
+    }
+
+    /**
+     * Updates the groups to reflect the differences between the old and new cache entries.
+     * Either of the old or new values can be <code>null</code>
+     * or contain a <code>null</code> group list, in which case the entry's
+     * groups will all be added or removed respectively.
+     *
+     * @param oldValue The old CacheEntry that is being replaced.
+     * @param newValue The new CacheEntry that is being inserted.
+     */
+    private void updateGroups(CacheEntry oldValue, CacheEntry newValue, boolean persist) {
+        Set oldGroups = null;
+        Set newGroups = null;
+
+        if (oldValue != null) {
+            oldGroups = oldValue.getGroups();
+        }
+
+        if (newValue != null) {
+            newGroups = newValue.getGroups();
+        }
+
+        // Get the names of the groups to remove
+        if (oldGroups != null) {
+            Set removeFromGroups = new HashSet();
+
+            for (Iterator it = oldGroups.iterator(); it.hasNext();) {
+                String groupName = (String) it.next();
+
+                if ((newGroups == null) || !newGroups.contains(groupName)) {
+                    // We need to remove this group
+                    removeFromGroups.add(groupName);
+                }
+            }
+
+            removeGroupMappings(oldValue.getKey(), removeFromGroups, persist);
+        }
+
+        // Get the names of the groups to add
+        if (newGroups != null) {
+            Set addToGroups = new HashSet();
+
+            for (Iterator it = newGroups.iterator(); it.hasNext();) {
+                String groupName = (String) it.next();
+
+                if ((oldGroups == null) || !oldGroups.contains(groupName)) {
+                    // We need to add this group
+                    addToGroups.add(groupName);
+                }
+            }
+
+            addGroupMappings(newValue.getKey(), addToGroups, persist, true);
+        }
+    }
+
+    /**
+     * AbstractConcurrentReadCache collision list entry.
+     */
+    protected static class Entry implements Map.Entry {
+        protected final Entry next;
+        protected final Object key;
+
+        /*
+           The use of volatile for value field ensures that
+           we can detect status changes without synchronization.
+           The other fields are never changed, and are
+           marked as final.
+        */
+        protected final int hash;
+        protected volatile Object value;
+
+        Entry(int hash, Object key, Object value, Entry next) {
+            this.hash = hash;
+            this.key = key;
+            this.next = next;
+            this.value = value;
+        }
+
+        // Map.Entry Ops
+        public Object getKey() {
+            return key;
+        }
+
+        /**
+         * Set the value of this entry.
+         * Note: In an entrySet or
+         * entrySet.iterator), unless the set or iterator is used under
+         * synchronization of the table as a whole (or you can otherwise
+         * guarantee lack of concurrent modification), <tt>setValue</tt>
+         * is not strictly guaranteed to actually replace the value field
+         * obtained via the <tt>get</tt> operation of the underlying hash
+         * table in multithreaded applications.  If iterator-wide
+         * synchronization is not used, and any other concurrent
+         * <tt>put</tt> or <tt>remove</tt> operations occur, sometimes
+         * even to <em>other</em> entries, then this change is not
+         * guaranteed to be reflected in the hash table. (It might, or it
+         * might not. There are no assurances either way.)
+         *
+         * @param      value   the new value.
+         * @return     the previous value, or null if entry has been detectably
+         * removed.
+         * @exception  NullPointerException  if the value is <code>null</code>.
+         *
+         **/
+        public Object setValue(Object value) {
+            if (value == null) {
+                throw new NullPointerException();
+            }
+
+            Object oldValue = this.value;
+            this.value = value;
+
+            return oldValue;
+        }
+
+        /**
+         * Get the value.
+         * Note: In an entrySet or entrySet.iterator,
+         * unless the set or iterator is used under synchronization of the
+         * table as a whole (or you can otherwise guarantee lack of
+         * concurrent modification), <tt>getValue</tt> <em>might</em>
+         * return null, reflecting the fact that the entry has been
+         * concurrently removed. However, there are no assurances that
+         * concurrent removals will be reflected using this method.
+         *
+         * @return     the current value, or null if the entry has been
+         * detectably removed.
+         **/
+        public Object getValue() {
+            return value;
+        }
+
+        public boolean equals(Object o) {
+            if (!(o instanceof Map.Entry)) {
+                return false;
+            }
+
+            Map.Entry e = (Map.Entry) o;
+
+            if (!key.equals(e.getKey())) {
+                return false;
+            }
+
+            Object v = value;
+
+            return (v == null) ? (e.getValue() == null) : v.equals(e.getValue());
+        }
+
+        public int hashCode() {
+            Object v = value;
+
+            return hash ^ ((v == null) ? 0 : v.hashCode());
+        }
+
+        public String toString() {
+            return key + "=" + value;
+        }
+
+        protected Object clone() {
+            return new Entry(hash, key, value, ((next == null) ? null : (Entry) next.clone()));
+        }
+    }
+
+    protected class HashIterator implements Iterator, Enumeration {
+        protected final Entry[] tab; // snapshot of table
+        protected Entry entry = null; // current node of slot
+        protected Entry lastReturned = null; // last node returned by next
+        protected Object currentKey; // key for current node
+        protected Object currentValue; // value for current node
+        protected int index; // current slot
+
+        protected HashIterator() {
+            tab = AbstractConcurrentReadCache.this.getTableForReading();
+            index = tab.length - 1;
+        }
+
+        public boolean hasMoreElements() {
+            return hasNext();
+        }
+
+        public boolean hasNext() {
+            /*
+              currentkey and currentValue are set here to ensure that next()
+              returns normally if hasNext() returns true. This avoids
+              surprises especially when final element is removed during
+              traversal -- instead, we just ignore the removal during
+              current traversal.
+            */
+            for (;;) {
+                if (entry != null) {
+                    Object v = entry.value;
+
+                    if (v != null) {
+                        currentKey = entry.key;
+                        currentValue = v;
+
+                        return true;
+                    } else {
+                        entry = entry.next;
+                    }
+                }
+
+                while ((entry == null) && (index >= 0)) {
+                    entry = tab[index--];
+                }
+
+                if (entry == null) {
+                    currentKey = currentValue = null;
+
+                    return false;
+                }
+            }
+        }
+
+        public Object next() {
+            if ((currentKey == null) && !hasNext()) {
+                throw new NoSuchElementException();
+            }
+
+            Object result = returnValueOfNext();
+            lastReturned = entry;
+            currentKey = currentValue = null;
+            entry = entry.next;
+
+            return result;
+        }
+
+        public Object nextElement() {
+            return next();
+        }
+
+        public void remove() {
+            if (lastReturned == null) {
+                throw new IllegalStateException();
+            }
+
+            AbstractConcurrentReadCache.this.remove(lastReturned.key);
+        }
+
+        protected Object returnValueOfNext() {
+            return entry;
+        }
+    }
+
+    protected class KeyIterator extends HashIterator {
+        protected Object returnValueOfNext() {
+            return currentKey;
+        }
+    }
+
+    protected class ValueIterator extends HashIterator {
+        protected Object returnValueOfNext() {
+            return currentValue;
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/algorithm/FIFOCache.java b/src/java/com/opensymphony/oscache/base/algorithm/FIFOCache.java
new file mode 100644
index 0000000..b2b047f
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/algorithm/FIFOCache.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import java.util.*;
+
+/**
+ * FIFO (First In First Out) based queue algorithm for the cache.
+ *
+ * No synchronization is required in this class since the
+ * <code>AbstractConcurrentReadCache</code> already takes care of any
+ * synchronization requirements.
+ *
+ * @version        $Revision: 427 $
+ * @author        <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author        <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author        <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class FIFOCache extends AbstractConcurrentReadCache {
+
+    private static final long serialVersionUID = -10333778645392679L;
+
+    /**
+     * A queue containing all cache keys
+     */
+    private Collection list = new LinkedHashSet();
+
+    /**
+     * Constructs a FIFO Cache.
+     */
+    public FIFOCache() {
+        super();
+    }
+
+    /**
+     * Constructs a FIFO Cache of the specified capacity.
+     *
+     * @param capacity The maximum cache capacity.
+     */
+    public FIFOCache(int capacity) {
+        this();
+        maxEntries = capacity;
+    }
+
+    /**
+     * An object was retrieved from the cache. This implementation
+     * does noting since this event has no impact on the FIFO algorithm.
+     *
+     * @param key The cache key of the item that was retrieved.
+     */
+    protected void itemRetrieved(Object key) {
+    }
+
+    /**
+     * An object was put in the cache. This implementation just adds
+     * the key to the end of the list if it doesn't exist in the list
+     * already.
+     *
+     * @param key The cache key of the item that was put.
+     */
+    protected void itemPut(Object key) {
+        if (!list.contains(key)) {
+            list.add(key);
+        }
+    }
+
+    /**
+     * An item needs to be removed from the cache. The FIFO implementation
+     * removes the first element in the list (ie, the item that has been in
+     * the cache for the longest time).
+     *
+     * @return The key of whichever item was removed.
+     */
+    protected Object removeItem() {
+        Iterator it = list.iterator();
+        Object toRemove = it.next();
+        it.remove();
+
+        return toRemove;
+    }
+
+    /**
+     * Remove specified key since that object has been removed from the cache.
+     *
+     * @param key The cache key of the item that was removed.
+     */
+    protected void itemRemoved(Object key) {
+        list.remove(key);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/algorithm/LRUCache.java b/src/java/com/opensymphony/oscache/base/algorithm/LRUCache.java
new file mode 100644
index 0000000..5dd8472
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/algorithm/LRUCache.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import java.util.*;
+
+/**
+ * <p>LRU (Least Recently Used) algorithm for the cache.</p>
+ *
+ * <p>Since release 2.3 this class requires Java 1.4 
+ * to use the <code>LinkedHashSet</code>. Use prior OSCache release which
+ * require the Jakarta commons-collections <code>SequencedHashMap</code>
+ * class or the <code>LinkedList</code> class if neither of the above
+ * classes are available.</p>
+ *
+ * <p>No synchronization is required in this class since the
+ * <code>AbstractConcurrentReadCache</code> already takes care of any
+ * synchronization requirements.</p>
+ *
+ * @version        $Revision: 427 $
+ * @author <a href="mailto:salaman at teknos.com">Victor Salaman</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class LRUCache extends AbstractConcurrentReadCache {
+	
+    private static final long serialVersionUID = -7379608101794788534L;
+
+    /**
+     * Cache queue containing all cache keys.
+     */
+    private Collection list = new LinkedHashSet();
+
+    /**
+     * A flag indicating whether there is a removal operation in progress.
+     */
+    private volatile boolean removeInProgress = false;
+
+    /**
+     * Constructs an LRU Cache.
+     */
+    public LRUCache() {
+        super();
+    }
+
+    /**
+     * Constructors a LRU Cache of the specified capacity.
+     *
+     * @param capacity The maximum cache capacity.
+     */
+    public LRUCache(int capacity) {
+        this();
+        maxEntries = capacity;
+    }
+
+    /**
+     * An item was retrieved from the list. The LRU implementation moves
+     * the retrieved item's key to the front of the list.
+     *
+     * @param key The cache key of the item that was retrieved.
+     */
+    protected void itemRetrieved(Object key) {
+        // Prevent list operations during remove
+        while (removeInProgress) {
+            try {
+                Thread.sleep(5);
+            } catch (InterruptedException ie) {
+            }
+        }
+
+        // We need to synchronize here because AbstractConcurrentReadCache
+        // doesn't prevent multiple threads from calling this method simultaneously.
+        synchronized (list) {
+            list.remove(key);
+            list.add(key);
+        }
+    }
+
+    /**
+     * An object was put in the cache. This implementation adds/moves the
+     * key to the end of the list.
+     *
+     * @param key The cache key of the item that was put.
+     */
+    protected void itemPut(Object key) {
+        // Since this entry was just accessed, move it to the back of the list.
+    	synchronized (list) { // A further fix for CACHE-44
+    		list.remove(key);
+            list.add(key);
+        }
+    }
+
+    /**
+     * An item needs to be removed from the cache. The LRU implementation
+     * removes the first element in the list (ie, the item that was least-recently
+     * accessed).
+     *
+     * @return The key of whichever item was removed.
+     */
+    protected Object removeItem() {
+        Object toRemove = null;
+
+        removeInProgress = true;
+        try {
+        	while (toRemove == null) {
+                try {
+                    toRemove = removeFirst();
+                } catch (Exception e) {
+                    // List is empty.
+                    // this is theorically possible if we have more than the size concurrent
+                    // thread in getItem. Remove completed but add not done yet.
+                    // We simply wait for add to complete.
+                    do {
+                        try {
+                            Thread.sleep(5);
+                        } catch (InterruptedException ie) {
+                        }
+                    } while (list.isEmpty());
+                }
+        	}
+        } finally {
+            removeInProgress = false;
+        }
+
+        return toRemove;
+    }
+
+    /**
+     * Remove specified key since that object has been removed from the cache.
+     *
+     * @param key The cache key of the item that was removed.
+     */
+    protected void itemRemoved(Object key) {
+        list.remove(key);
+    }
+
+    /**
+     * Removes the first object from the list of keys.
+     *
+     * @return the object that was removed
+     */
+    private Object removeFirst() {
+    	Object toRemove = null;
+    	
+    	synchronized (list) { // A further fix for CACHE-44 and CACHE-246
+        	Iterator it = list.iterator();
+        	toRemove = it.next();
+        	it.remove();
+    	}
+
+        return toRemove;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/algorithm/UnlimitedCache.java b/src/java/com/opensymphony/oscache/base/algorithm/UnlimitedCache.java
new file mode 100644
index 0000000..2a7695c
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/algorithm/UnlimitedCache.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * A simple unlimited cache that has no upper bound to the number of
+ * cache entries it can contain.
+ *
+ * @version        $Revision: 427 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class UnlimitedCache extends AbstractConcurrentReadCache {
+	
+    private static final long serialVersionUID = 7615611393249532285L;
+    
+    private final Log log = LogFactory.getLog(this.getClass());
+
+    /**
+     * Creates an unlimited cache by calling the super class's constructor
+     * with an <code>UNLIMITED</code> maximum number of entries.
+     */
+    public UnlimitedCache() {
+        super();
+        maxEntries = UNLIMITED;
+    }
+
+    /**
+     * Overrides the <code>setMaxEntries</code> with an empty implementation.
+     * This property cannot be modified and is ignored for an
+     * <code>UnlimitedCache</code>.
+     */
+    public void setMaxEntries(int maxEntries) {
+    	log.warn("Cache max entries can't be set in " + this.getClass().getName() + ", ignoring value " + maxEntries + ".");
+    }
+
+    /**
+     * Implements <code>itemRetrieved</code> with an empty implementation.
+     * The unlimited cache doesn't care that an item was retrieved.
+     */
+    protected void itemRetrieved(Object key) {
+    }
+
+    /**
+     * Implements <code>itemPut</code> with an empty implementation.
+     * The unlimited cache doesn't care that an item was put in the cache.
+     */
+    protected void itemPut(Object key) {
+    }
+
+    /**
+     * This method just returns <code>null</code> since items should
+     * never end up being removed from an unlimited cache!
+     */
+    protected Object removeItem() {
+        return null;
+    }
+
+    /**
+     * An empty implementation. The unlimited cache doesn't care that an
+     * item was removed.
+     */
+    protected void itemRemoved(Object key) {
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/algorithm/package.html b/src/java/com/opensymphony/oscache/base/algorithm/package.html
new file mode 100644
index 0000000..abf5529
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/algorithm/package.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the classes that implement the caching algorithms used by OSCache, all of
+which are based on a derivative of Doug Lea's <code>ConcurrentReaderHashMap</code>.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+For further information on Doug Lea's concurrency package, please see:
+<ul>
+  <li><a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html">http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheEntryEvent.java b/src/java/com/opensymphony/oscache/base/events/CacheEntryEvent.java
new file mode 100644
index 0000000..f4357ac
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheEntryEvent.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.CacheEntry;
+
+/**
+ * CacheEntryEvent is the object created when an event occurs on a
+ * cache entry (Add, update, remove, flush). It contains the entry itself and
+ * its map.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class CacheEntryEvent extends CacheEvent {
+    /**
+     * The cache where the entry resides.
+     */
+    private Cache map = null;
+
+    /**
+     * The entry that the event applies to.
+     */
+    private CacheEntry entry = null;
+
+    /**
+     * Constructs a cache entry event object with no specified origin
+     *
+     * @param map     The cache map of the cache entry
+     * @param entry   The cache entry that the event applies to
+     */
+    public CacheEntryEvent(Cache map, CacheEntry entry) {
+        this(map, entry, null);
+    }
+
+    /**
+     * Constructs a cache entry event object
+     *
+     * @param map     The cache map of the cache entry
+     * @param entry   The cache entry that the event applies to
+     * @param origin  The origin of this event
+     */
+    public CacheEntryEvent(Cache map, CacheEntry entry, String origin) {
+        super(origin);
+        this.map = map;
+        this.entry = entry;
+    }
+
+    /**
+     * Retrieve the cache entry that the event applies to.
+     */
+    public CacheEntry getEntry() {
+        return entry;
+    }
+
+    /**
+     * Retrieve the cache entry key
+     */
+    public String getKey() {
+        return entry.getKey();
+    }
+
+    /**
+     * Retrieve the cache map where the entry resides.
+     */
+    public Cache getMap() {
+        return map;
+    }
+
+    public String toString() {
+        return "key=" + entry.getKey();
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheEntryEventListener.java b/src/java/com/opensymphony/oscache/base/events/CacheEntryEventListener.java
new file mode 100644
index 0000000..d1b463a
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheEntryEventListener.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is the interface to listen to cache entry events. There is a method
+ * for each event type. These methods are called via a dispatcher. If you
+ * want to be notified when an event occurs on an entry, group or across a
+ * pattern, register a listener and implement this interface.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface CacheEntryEventListener extends CacheEventListener {
+    /**
+     * Event fired when an entry is added to the cache.
+     */
+    void cacheEntryAdded(CacheEntryEvent event);
+
+    /**
+     * Event fired when an entry is flushed from the cache.
+     */
+    void cacheEntryFlushed(CacheEntryEvent event);
+
+    /**
+     * Event fired when an entry is removed from the cache.
+     */
+    void cacheEntryRemoved(CacheEntryEvent event);
+
+    /**
+     * Event fired when an entry is updated in the cache.
+     */
+    void cacheEntryUpdated(CacheEntryEvent event);
+
+    /**
+     * Event fired when a group is flushed from the cache.
+     */
+    void cacheGroupFlushed(CacheGroupEvent event);
+
+    /**
+     * Event fired when a key pattern is flushed from the cache.
+     * Note that this event will <em>not</em> be fired if the pattern
+     * is <code>null</code> or an empty string, instead the flush
+     * request will silently be ignored.
+     */
+    void cachePatternFlushed(CachePatternEvent event);
+
+    /**
+     * An event that is fired when an entire cache gets flushed.
+     */
+    void cacheFlushed(CachewideEvent event);
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheEntryEventType.java b/src/java/com/opensymphony/oscache/base/events/CacheEntryEventType.java
new file mode 100644
index 0000000..d4c7b88
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheEntryEventType.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is all the possible events that may occur on a cache entry or
+ * collection of cache entries.<p>
+ * There is a corresponding interface {@link CacheEntryEventListener} for
+ * handling these events.
+ *
+ * @version        $Revision: 387 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class CacheEntryEventType {
+    /**
+     * Get an event type for an entry added.
+     */
+    public static final CacheEntryEventType ENTRY_ADDED = new CacheEntryEventType();
+
+    /**
+     * Get an event type for an entry updated.
+     */
+    public static final CacheEntryEventType ENTRY_UPDATED = new CacheEntryEventType();
+
+    /**
+     * Get an event type for an entry flushed.
+     */
+    public static final CacheEntryEventType ENTRY_FLUSHED = new CacheEntryEventType();
+
+    /**
+     * Get an event type for an entry removed.
+     */
+    public static final CacheEntryEventType ENTRY_REMOVED = new CacheEntryEventType();
+
+    /**
+     * Get an event type for a group flush event.
+     */
+    public static final CacheEntryEventType GROUP_FLUSHED = new CacheEntryEventType();
+
+    /**
+     * Get an event type for a pattern flush event.
+     */
+    public static final CacheEntryEventType PATTERN_FLUSHED = new CacheEntryEventType();
+
+    /**
+     * Private constructor to ensure that no object of that type are
+     * created externally.
+     */
+    private CacheEntryEventType() {
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheEvent.java b/src/java/com/opensymphony/oscache/base/events/CacheEvent.java
new file mode 100644
index 0000000..796327a
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * The root event class for all cache events. Each subclasses of this class
+ * classifies a particular type of cache event.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * Date: 20-May-2003
+ * Time: 15:25:02
+ */
+public abstract class CacheEvent {
+    /**
+     * An optional tag that can be attached to the event to specify the event's origin.
+     */
+    protected String origin = null;
+
+    /**
+     * No-argument constructor so subtypes can easily implement <code>Serializable</code>
+     */
+    public CacheEvent() {
+    }
+
+    /**
+     * Creates a cache event object that came from the specified origin.
+     *
+     * @param origin A string that indicates where this event was fired from.
+     * This value is optional; <code>null</code> can be passed in if an
+     * origin is not required.
+     */
+    public CacheEvent(String origin) {
+        this.origin = origin;
+    }
+
+    /**
+     * Retrieves the origin of this event, if one was specified. This is most
+     * useful when an event handler causes another event to fire - by checking
+     * the origin the handler is able to prevent recursive events being
+     * fired.
+     */
+    public String getOrigin() {
+        return origin;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheEventListener.java b/src/java/com/opensymphony/oscache/base/events/CacheEventListener.java
new file mode 100644
index 0000000..414be22
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheEventListener.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import java.util.EventListener;
+
+/**
+ * This is the base interface for cache events.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public interface CacheEventListener extends EventListener {
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheGroupEvent.java b/src/java/com/opensymphony/oscache/base/events/CacheGroupEvent.java
new file mode 100644
index 0000000..13517b0
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheGroupEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+
+/**
+ * CacheGroupEvent is an event that occurs at the cache group level
+ * (Add, update, remove, flush). It contains the group name and the
+ * originating cache object.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class CacheGroupEvent extends CacheEvent {
+    /**
+     * The cache where the entry resides.
+     */
+    private Cache map = null;
+
+    /**
+     * The group that the event applies to.
+     */
+    private String group = null;
+
+    /**
+     * Constructs a cache group event with no origin
+     *
+     * @param map     The cache map of the cache entry
+     * @param group   The cache group that the event applies to.
+     */
+    public CacheGroupEvent(Cache map, String group) {
+        this(map, group, null);
+    }
+
+    /**
+     * Constructs a cache group event
+     *
+     * @param map     The cache map of the cache entry
+     * @param group   The cache group that the event applies to.
+     * @param origin  An optional tag that can be attached to the event to
+     * specify the event's origin. This is useful to prevent events from being
+     * fired recursively in some situations, such as when an event handler
+     * causes another event to be fired.
+     */
+    public CacheGroupEvent(Cache map, String group, String origin) {
+        super(origin);
+        this.map = map;
+        this.group = group;
+    }
+
+    /**
+     * Retrieve the cache group that the event applies to.
+     */
+    public String getGroup() {
+        return group;
+    }
+
+    /**
+     * Retrieve the cache map where the group resides.
+     */
+    public Cache getMap() {
+        return map;
+    }
+
+    public String toString() {
+        return "groupName=" + group;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEvent.java b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEvent.java
new file mode 100644
index 0000000..0c2c18f
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.CacheEntry;
+
+/**
+ * Cache map access event. This is the object created when an event occurs on a
+ * cache map (cache Hit, cache miss). It contains the entry that was referenced
+ * by the event and the event type.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class CacheMapAccessEvent extends CacheEvent {
+    /**
+     * The cache entry that the event applies to.
+     */
+    private CacheEntry entry = null;
+
+    /**
+     * Type of the event.
+     */
+    private CacheMapAccessEventType eventType = null;
+
+    /**
+     * Constructor.
+     * <p>
+     * @param eventType   Type of the event.
+     * @param entry       The cache entry that the event applies to.
+     */
+    public CacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry) {
+        this(eventType, entry, null);
+    }
+
+    /**
+     * Constructor.
+     * <p>
+     * @param eventType   Type of the event.
+     * @param entry       The cache entry that the event applies to.
+     * @param origin      The origin of the event
+     */
+    public CacheMapAccessEvent(CacheMapAccessEventType eventType, CacheEntry entry, String origin) {
+        super(origin);
+        this.eventType = eventType;
+        this.entry = entry;
+    }
+
+    /**
+     * Retrieve the cache entry that the event applies to.
+     */
+    public CacheEntry getCacheEntry() {
+        return entry;
+    }
+
+    /**
+     * Retrieve the cache entry key that the event applies to.
+     */
+    public String getCacheEntryKey() {
+        return entry.getKey();
+    }
+
+    /**
+     * Retrieve the type of the event.
+     */
+    public CacheMapAccessEventType getEventType() {
+        return eventType;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventListener.java b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventListener.java
new file mode 100644
index 0000000..c2e506a
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is the interface to listen to cache map access events. The events are
+ * cache hits and misses, and are dispatched through this interface
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface CacheMapAccessEventListener extends CacheEventListener {
+    /**
+     * Event fired when an entry is accessed.
+     * Use getEventType to differentiate between access events.
+     */
+    public void accessed(CacheMapAccessEvent event);
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventType.java b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventType.java
new file mode 100644
index 0000000..1b967de
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CacheMapAccessEventType.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is an enumeration of the cache events that represent the
+ * various outcomes of cache accesses.
+ *
+ * @version $Revision: 387 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class CacheMapAccessEventType {
+    /**
+     * Get an event type for a cache hit.
+     */
+    public static final CacheMapAccessEventType HIT = new CacheMapAccessEventType();
+
+    /**
+     * Get an event type for a cache miss.
+     */
+    public static final CacheMapAccessEventType MISS = new CacheMapAccessEventType();
+
+    /**
+     * Get an event type for when the data was found in the cache but was stale.
+     */
+    public static final CacheMapAccessEventType STALE_HIT = new CacheMapAccessEventType();
+
+    /**
+     * Private constructor to ensure that no object of this type are
+     * created externally.
+     */
+    private CacheMapAccessEventType() {
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CachePatternEvent.java b/src/java/com/opensymphony/oscache/base/events/CachePatternEvent.java
new file mode 100644
index 0000000..278d7c4
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CachePatternEvent.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+
+/**
+ * A CachePatternEvent is fired when a pattern has been applied to a cache.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class CachePatternEvent extends CacheEvent {
+    /**
+     * The cache the pattern is being applied to.
+     */
+    private Cache map = null;
+
+    /**
+     * The pattern that is being applied.
+     */
+    private String pattern = null;
+
+    /**
+     * Constructs a cache pattern event with no origin
+     *
+     * @param map     The cache map that the pattern was applied to
+     * @param pattern The pattern that was applied
+     */
+    public CachePatternEvent(Cache map, String pattern) {
+        this(map, pattern, null);
+    }
+
+    /**
+     * Constructs a cache pattern event
+     *
+     * @param map     The cache map that the pattern was applied to
+     * @param pattern The cache pattern that the event applies to.
+     * @param origin  An optional tag that can be attached to the event to
+     * specify the event's origin. This is useful to prevent events from being
+     * fired recursively in some situations, such as when an event handler
+     * causes another event to be fired, or for logging purposes.
+     */
+    public CachePatternEvent(Cache map, String pattern, String origin) {
+        super(origin);
+        this.map = map;
+        this.pattern = pattern;
+    }
+
+    /**
+     * Retrieve the cache map that had the pattern applied.
+     */
+    public Cache getMap() {
+        return map;
+    }
+
+    /**
+     * Retrieve the pattern that was applied to the cache.
+     */
+    public String getPattern() {
+        return pattern;
+    }
+
+    public String toString() {
+        return "pattern=" + pattern;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CachewideEvent.java b/src/java/com/opensymphony/oscache/base/events/CachewideEvent.java
new file mode 100644
index 0000000..79f8ea6
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CachewideEvent.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+
+import java.util.Date;
+
+/**
+ * A <code>CachewideEvent<code> represents and event that occurs on
+ * the the entire cache, eg a cache flush or clear.
+ *
+ * @version $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class CachewideEvent extends CacheEvent {
+    /**
+     * The cache where the event occurred.
+     */
+    private Cache cache = null;
+
+    /**
+     * The date/time for when the flush is scheduled
+     */
+    private Date date = null;
+
+    /**
+     * Constructs a cachewide event with the specified origin.
+     *
+     * @param cache   The cache map that the event occurred on.
+     * @param date    The date/time that this cachewide event is scheduled for
+     * (eg, the date that the cache is to be flushed).
+     * @param origin  An optional tag that can be attached to the event to
+     * specify the event's origin. This is useful to prevent events from being
+     * fired recursively in some situations, such as when an event handler
+     * causes another event to be fired.
+     */
+    public CachewideEvent(Cache cache, Date date, String origin) {
+        super(origin);
+        this.date = date;
+        this.cache = cache;
+    }
+
+    /**
+     * Retrieve the cache map that the event occurred on.
+     */
+    public Cache getCache() {
+        return cache;
+    }
+
+    /**
+     * Retrieve the date/time that the cache flush is scheduled for.
+     */
+    public Date getDate() {
+        return date;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/CachewideEventType.java b/src/java/com/opensymphony/oscache/base/events/CachewideEventType.java
new file mode 100644
index 0000000..50a58a3
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/CachewideEventType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is an enumeration holding all the events that can
+ * occur at the cache-wide level.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class CachewideEventType {
+    /**
+     * Get an event type for a cache flush event.
+     */
+    public static final CachewideEventType CACHE_FLUSHED = new CachewideEventType();
+
+    /**
+     * Private constructor to ensure that no object of this type are
+     * created externally.
+     */
+    private CachewideEventType() {
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/ScopeEvent.java b/src/java/com/opensymphony/oscache/base/events/ScopeEvent.java
new file mode 100644
index 0000000..8b11edb
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/ScopeEvent.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import java.util.Date;
+
+/**
+ * A <code>ScopeEvent</code> is created when an event occurs across one or all scopes.
+ * This type of event is only applicable to the <code>ServletCacheAdministrator</code>.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class ScopeEvent extends CacheEvent {
+    /**
+     * Date that the event applies to.
+     */
+    private Date date = null;
+
+    /**
+     * Type of the event.
+     */
+    private ScopeEventType eventType = null;
+
+    /**
+     * Scope that applies to this event.
+     */
+    private int scope = 0;
+
+    /**
+     * Constructs a scope event object with no specified origin.
+     *
+     * @param eventType   Type of the event.
+     * @param scope       Scope that applies to the event.
+     * @param date        Date that the event applies to.
+     */
+    public ScopeEvent(ScopeEventType eventType, int scope, Date date) {
+        this(eventType, scope, date, null);
+    }
+
+    /**
+     * Constructs a scope event object.
+     *
+     * @param eventType   Type of the event.
+     * @param scope       Scope that applies to the event.
+     * @param date        Date that the event applies to.
+     * @param origin      The origin of this event.
+     */
+    public ScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
+        super(origin);
+        this.eventType = eventType;
+        this.scope = scope;
+        this.date = date;
+    }
+
+    /**
+     * Retrieve the event date
+     */
+    public Date getDate() {
+        return date;
+    }
+
+    /**
+     * Retrieve the type of the event.
+     */
+    public ScopeEventType getEventType() {
+        return eventType;
+    }
+
+    /**
+     * Retrieve the scope that applies to the event.
+     */
+    public int getScope() {
+        return scope;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/ScopeEventListener.java b/src/java/com/opensymphony/oscache/base/events/ScopeEventListener.java
new file mode 100644
index 0000000..ab1bf90
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/ScopeEventListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is the interface to listen to scope events. The events are
+ * scope flushed and all scope flushed, and are dispatched thru this interface
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface ScopeEventListener extends CacheEventListener {
+    /**
+     * Event fired when a specific or all scopes are flushed.
+     * Use getEventType to differentiate between the two.
+     */
+    public void scopeFlushed(ScopeEvent event);
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/ScopeEventType.java b/src/java/com/opensymphony/oscache/base/events/ScopeEventType.java
new file mode 100644
index 0000000..bc95f84
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/ScopeEventType.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+
+/**
+ * This is an enumeration of all the possible events that may occur
+ * at the scope level. Scope-level events are only relevant to the
+ * <code>ServletCacheAdministrator</code>.
+ *
+ * @version        $Revision: 387 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class ScopeEventType {
+    /**
+     * Specifies an event type for the all scope flushed event.
+     */
+    public static final ScopeEventType ALL_SCOPES_FLUSHED = new ScopeEventType();
+
+    /**
+     * Specifies an event type for the flushing of a  specific scope.
+     */
+    public static final ScopeEventType SCOPE_FLUSHED = new ScopeEventType();
+
+    /**
+     * Private constructor to ensure that no object of that type are
+     * created externally.
+     */
+    private ScopeEventType() {
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/events/package.html b/src/java/com/opensymphony/oscache/base/events/package.html
new file mode 100644
index 0000000..07038f6
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/events/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the base classes and interfaces that allow pluggable event handlers to be
+incorporated into OSCache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/base/package.html b/src/java/com/opensymphony/oscache/base/package.html
new file mode 100644
index 0000000..6198c60
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the base classes and interfaces that make up the core of OSCache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/base/persistence/CachePersistenceException.java b/src/java/com/opensymphony/oscache/base/persistence/CachePersistenceException.java
new file mode 100644
index 0000000..4e5be55
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/persistence/CachePersistenceException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.persistence;
+
+
+/**
+ * Exception thrown when an error occurs in a PersistenceListener implementation.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class CachePersistenceException extends Exception {
+    /**
+     * Creates new CachePersistenceException without detail message.
+     */
+    public CachePersistenceException() {
+    }
+
+    /**
+     * Constructs an CachePersistenceException with the specified detail message.
+     *
+     * @param msg the detail message.
+     */
+    public CachePersistenceException(String msg) {
+        super(msg);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/base/persistence/PersistenceListener.java b/src/java/com/opensymphony/oscache/base/persistence/PersistenceListener.java
new file mode 100644
index 0000000..e146fcb
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/persistence/PersistenceListener.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.persistence;
+
+import com.opensymphony.oscache.base.Config;
+
+import java.util.Set;
+
+/**
+ * Defines the methods that are required to persist cache data.
+ * To provide a custom persistence mechanism you should implement this
+ * interface and supply the fully-qualified classname to the cache via
+ * the <code>cache.persistence.class</code> configuration property.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface PersistenceListener {
+    /**
+     * Verify if an object is currently stored in the persistent cache.
+     *
+     * @param key The cache key of the object to check.
+     */
+    public boolean isStored(String key) throws CachePersistenceException;
+
+    /**
+     * Verify if a group is currently stored in the persistent cache.
+     *
+     * @param groupName The name of the group to check.
+     */
+    public boolean isGroupStored(String groupName) throws CachePersistenceException;
+
+    /**
+     * Clear the entire persistent cache (including the root)
+     */
+    public void clear() throws CachePersistenceException;
+
+    /**
+     * Allow the persistence code to initialize itself based on the supplied
+     * cache configuration.
+     */
+    public PersistenceListener configure(Config config);
+
+    /**
+     * Removes an object from the persistent cache
+     */
+    public void remove(String key) throws CachePersistenceException;
+
+    /**
+     * Removes a group from the persistent cache.
+     *
+     * @param groupName The name of the cache group to remove.
+     */
+    public void removeGroup(String groupName) throws CachePersistenceException;
+
+    /**
+     * Retrieves an object from the persistent cache.
+     *
+     * @param key The unique cache key that maps to the object
+     * being retrieved.
+     * @return The object, or <code>null</code> if no object was found
+     * matching the supplied key.
+     */
+    public Object retrieve(String key) throws CachePersistenceException;
+
+    /**
+     * Stores an object in the persistent cache.
+     *
+     * @param key The key to uniquely identify this object.
+     * @param obj The object to persist. Most implementations
+     * of this interface will require this object implements
+     * <code>Serializable</code>.
+     */
+    public void store(String key, Object obj) throws CachePersistenceException;
+
+    /**
+     * Stores a group in the persistent cache.
+     *
+     * @param groupName The name of the group to persist.
+     * @param group A set containing the keys of all the <code>CacheEntry</code>
+     * objects that belong to this group.
+     */
+    public void storeGroup(String groupName, Set group) throws CachePersistenceException;
+
+    /**
+     * Retrieves a group from the persistent cache.
+     *
+     * @param groupName The name of the group to retrieve.
+     * @return The returned set should contain the keys
+     * of all the <code>CacheEntry</code> objects that belong
+     * to this group.
+     */
+    Set retrieveGroup(String groupName) throws CachePersistenceException;
+}
diff --git a/src/java/com/opensymphony/oscache/base/persistence/package.html b/src/java/com/opensymphony/oscache/base/persistence/package.html
new file mode 100644
index 0000000..a75d087
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/base/persistence/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the interfaces that provide persistence storage of cached objects.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/extra/CacheEntryEventListenerImpl.java b/src/java/com/opensymphony/oscache/extra/CacheEntryEventListenerImpl.java
new file mode 100644
index 0000000..6d3ed91
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/extra/CacheEntryEventListenerImpl.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.events.*;
+
+/**
+ * Implementation of a CacheEntryEventListener. It use the events to count
+ * the operations performed on the cache.
+ * <p>
+ * We are not using any synchronized so that this does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class CacheEntryEventListenerImpl implements CacheEntryEventListener {
+    /**
+     * Counter for the cache flushes
+     */
+    private int cacheFlushedCount = 0;
+
+    /**
+     * Counter for the added entries
+     */
+    private int entryAddedCount = 0;
+
+    /**
+     * Counter for the flushed entries
+     */
+    private int entryFlushedCount = 0;
+
+    /**
+     * Counter for the removed entries
+     */
+    private int entryRemovedCount = 0;
+
+    /**
+     * Counter for the updated entries
+     */
+    private int entryUpdatedCount = 0;
+
+    /**
+     * Counter for the flushed groups
+     */
+    private int groupFlushedCount = 0;
+
+    /**
+     * Counter for the pattern flushes
+     */
+    private int patternFlushedCount = 0;
+
+    /**
+     * Constructor, empty for us
+     */
+    public CacheEntryEventListenerImpl() {
+    }
+
+    /**
+     * Gets the add counter
+     *
+     * @return The added counter
+     */
+    public int getEntryAddedCount() {
+        return entryAddedCount;
+    }
+
+    /**
+     * Gets the flushed counter
+     *
+     * @return The flushed counter
+     */
+    public int getEntryFlushedCount() {
+        return entryFlushedCount;
+    }
+
+    /**
+     * Gets the removed counter
+     *
+     * @return The removed counter
+     */
+    public int getEntryRemovedCount() {
+        return entryRemovedCount;
+    }
+
+    /**
+     * Gets the updated counter
+     *
+     * @return The updated counter
+     */
+    public int getEntryUpdatedCount() {
+        return entryUpdatedCount;
+    }
+
+    /**
+     * Gets the group flush counter
+     *
+     * @return The number of group flush calls that have occurred
+     */
+    public int getGroupFlushedCount() {
+        return groupFlushedCount;
+    }
+
+    /**
+     * Gets the pattern flush counter
+     *
+     * @return The number of pattern flush calls that have occurred
+     */
+    public int getPatternFlushedCount() {
+        return patternFlushedCount;
+    }
+
+    /**
+     * Gets the cache flush counter
+     *
+     * @return The number of times the entire cache has been flushed
+     */
+    public int getCacheFlushedCount() {
+        return cacheFlushedCount;
+    }
+
+    /**
+     * Handles the event fired when an entry is added in the cache.
+     *
+     * @param event The event triggered when a cache entry has been added
+     */
+    public void cacheEntryAdded(CacheEntryEvent event) {
+        entryAddedCount++;
+    }
+
+    /**
+     * Handles the event fired when an entry is flushed from the cache.
+     *
+     * @param event The event triggered when a cache entry has been flushed
+     */
+    public void cacheEntryFlushed(CacheEntryEvent event) {
+        entryFlushedCount++;
+    }
+
+    /**
+     * Handles the event fired when an entry is removed from the cache.
+     *
+     * @param event The event triggered when a cache entry has been removed
+     */
+    public void cacheEntryRemoved(CacheEntryEvent event) {
+        entryRemovedCount++;
+    }
+
+    /**
+     * Handles the event fired when an entry is updated in the cache.
+     *
+     * @param event The event triggered when a cache entry has been updated
+     */
+    public void cacheEntryUpdated(CacheEntryEvent event) {
+        entryUpdatedCount++;
+    }
+
+    /**
+     * Handles the event fired when a group is flushed from the cache.
+     *
+     * @param event The event triggered when a cache group has been flushed
+     */
+    public void cacheGroupFlushed(CacheGroupEvent event) {
+        groupFlushedCount++;
+    }
+
+    /**
+     * Handles the event fired when a pattern is flushed from the cache.
+     *
+     * @param event The event triggered when a cache pattern has been flushed
+     */
+    public void cachePatternFlushed(CachePatternEvent event) {
+        patternFlushedCount++;
+    }
+
+    /**
+     * Handles the event fired when a cache flush occurs.
+     *
+     * @param event The event triggered when an entire cache is flushed
+     */
+    public void cacheFlushed(CachewideEvent event) {
+        cacheFlushedCount++;
+    }
+
+    /**
+     * Returns the internal values in a string form
+     */
+    public String toString() {
+        return ("Added " + entryAddedCount + ", Updated " + entryUpdatedCount + ", Flushed " + entryFlushedCount + ", Removed " + entryRemovedCount + ", Groups Flushed " + groupFlushedCount + ", Patterns Flushed " + patternFlushedCount + ", Cache Flushed " + cacheFlushedCount);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/extra/CacheMapAccessEventListenerImpl.java b/src/java/com/opensymphony/oscache/extra/CacheMapAccessEventListenerImpl.java
new file mode 100644
index 0000000..d20c0c2
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/extra/CacheMapAccessEventListenerImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.events.CacheMapAccessEvent;
+import com.opensymphony.oscache.base.events.CacheMapAccessEventListener;
+import com.opensymphony.oscache.base.events.CacheMapAccessEventType;
+
+/**
+ * Implementation of a CacheMapAccessEventListener. It uses the events to count
+ * the cache hit and misses.
+ * <p>
+ * We are not using any synchronized so that this does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class CacheMapAccessEventListenerImpl implements CacheMapAccessEventListener {
+    /**
+     * Hit counter
+     */
+    private int hitCount = 0;
+
+    /**
+     * Miss counter
+     */
+    private int missCount = 0;
+
+    /**
+     * Stale hit counter
+     */
+    private int staleHitCount = 0;
+
+    /**
+     * Constructor, empty for us
+     */
+    public CacheMapAccessEventListenerImpl() {
+    }
+
+    /**
+     * Returns the cache's current hit count
+     *
+     * @return The hit count
+     */
+    public int getHitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Returns the cache's current miss count
+     *
+     * @return The miss count
+     */
+    public int getMissCount() {
+        return missCount;
+    }
+
+    /**
+     * Returns the cache's current stale hit count
+     */
+    public int getStaleHitCount() {
+        return staleHitCount;
+    }
+
+    /**
+     * This method handles an event each time the cache is accessed
+     *
+     * @param event The event triggered when the cache was accessed
+     */
+    public void accessed(CacheMapAccessEvent event) {
+        // Retrieve the event type and update the counters
+        CacheMapAccessEventType type = event.getEventType();
+
+        // Handles a hit event
+        if (type == CacheMapAccessEventType.HIT) {
+            hitCount++;
+        }
+        // Handles a stale hit event
+        else if (type == CacheMapAccessEventType.STALE_HIT) {
+            staleHitCount++;
+        }
+        // Handles a miss event
+        else if (type == CacheMapAccessEventType.MISS) {
+            missCount++;
+        } else {
+            // Unknown event!
+            throw new IllegalArgumentException("Unknown Cache Map Access event received");
+        }
+    }
+
+    /**
+     * Resets all of the totals to zero
+     */
+    public void reset() {
+        hitCount = 0;
+        staleHitCount = 0;
+        missCount = 0;
+    }
+
+    /**
+     * Return the counters in a string form
+     */
+    public String toString() {
+        return ("Hit count = " + hitCount + ", stale hit count = " + staleHitCount + " and miss count = " + missCount);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/extra/ScopeEventListenerImpl.java b/src/java/com/opensymphony/oscache/extra/ScopeEventListenerImpl.java
new file mode 100644
index 0000000..f6219fc
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/extra/ScopeEventListenerImpl.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.events.ScopeEvent;
+import com.opensymphony.oscache.base.events.ScopeEventListener;
+import com.opensymphony.oscache.base.events.ScopeEventType;
+
+/**
+ * Implementation of a ScopeEventListener that keeps track of the scope flush events.
+ * We are not using any synchronized so that this does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class ScopeEventListenerImpl implements ScopeEventListener {
+    /**
+     * Scope names
+     */
+    public static final String[] SCOPE_NAMES = {
+        null, "page", "request", "session", "application"
+    };
+
+    /**
+     * Number of known scopes
+     */
+    public static final int NB_SCOPES = SCOPE_NAMES.length - 1;
+
+    /**
+     * Page scope number
+     */
+    public static final int PAGE_SCOPE = 1;
+
+    /**
+     * Request scope number
+     */
+    public static final int REQUEST_SCOPE = 2;
+
+    /**
+     * Session scope number
+     */
+    public static final int SESSION_SCOPE = 3;
+
+    /**
+     * Application scope number
+     */
+    public static final int APPLICATION_SCOPE = 4;
+
+    /**
+     * Flush counter for all scopes.
+     * Add one to the number of scope because the array is being used
+     * from position 1 instead of 0 for convenience
+     */
+    private int[] scopeFlushCount = new int[NB_SCOPES + 1];
+
+    public ScopeEventListenerImpl() {
+    }
+
+    /**
+     * Gets the flush count for scope {@link ScopeEventListenerImpl#APPLICATION_SCOPE}.
+     * <p>
+     * @return The total number of application flush
+     */
+    public int getApplicationScopeFlushCount() {
+        return scopeFlushCount[APPLICATION_SCOPE];
+    }
+
+    /**
+     * Gets the flush count for scope {@link ScopeEventListenerImpl#PAGE_SCOPE}.
+     * @return The total number of page flush
+     */
+    public int getPageScopeFlushCount() {
+        return scopeFlushCount[PAGE_SCOPE];
+    }
+
+    /**
+     * Gets the flush count for scope {@link ScopeEventListenerImpl#REQUEST_SCOPE}.
+     * @return The total number of request flush
+     */
+    public int getRequestScopeFlushCount() {
+        return scopeFlushCount[REQUEST_SCOPE];
+    }
+
+    /**
+     * Gets the flush count for scope {@link ScopeEventListenerImpl#SESSION_SCOPE}.
+     * @return The total number of session flush
+     */
+    public int getSessionScopeFlushCount() {
+        return scopeFlushCount[SESSION_SCOPE];
+    }
+
+    /**
+     * Returns the total flush count.
+     * @return The total number of scope flush
+     */
+    public int getTotalScopeFlushCount() {
+        int total = 0;
+
+        for (int count = 1; count <= NB_SCOPES; count++) {
+            total += scopeFlushCount[count];
+        }
+
+        return total;
+    }
+
+    /**
+     * Handles all the scope flush events.
+     * @param event The scope event
+     */
+    public void scopeFlushed(ScopeEvent event) {
+        // Get the event type and process it
+        ScopeEventType eventType = event.getEventType();
+
+        if (eventType == ScopeEventType.ALL_SCOPES_FLUSHED) {
+            // All 4 scopes were flushed, increment the counters
+            for (int count = 1; count <= NB_SCOPES; count++) {
+                scopeFlushCount[count]++;
+            }
+        } else if (eventType == ScopeEventType.SCOPE_FLUSHED) {
+            // Get back the scope from the event and increment the flush count
+            scopeFlushCount[event.getScope()]++;
+        } else {
+            // Unknown event!
+            throw new IllegalArgumentException("Unknown Scope Event type received");
+        }
+    }
+
+    /**
+     * Returns all the flush counter in a string form.
+     */
+    public String toString() {
+        StringBuffer returnString = new StringBuffer("Flush count for ");
+
+        for (int count = 1; count <= NB_SCOPES; count++) {
+            returnString.append("scope " + SCOPE_NAMES[count] + " = " + scopeFlushCount[count] + ", ");
+        }
+
+        // Remove the last 2 chars, which are ", "
+        returnString.setLength(returnString.length() - 2);
+
+        return returnString.toString();
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/extra/StatisticListenerImpl.java b/src/java/com/opensymphony/oscache/extra/StatisticListenerImpl.java
new file mode 100644
index 0000000..354e7d4
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/extra/StatisticListenerImpl.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.events.CacheEntryEvent;
+import com.opensymphony.oscache.base.events.CacheEntryEventListener;
+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.ScopeEvent;
+import com.opensymphony.oscache.base.events.ScopeEventListener;
+import com.opensymphony.oscache.extra.ScopeEventListenerImpl;
+
+/**
+ * A simple implementation of a statistic reporter which uses the
+ * event listeners. It uses the events to count the cache hit and
+ * misses and of course the flushes.
+ * <p>
+ * We are not using any synchronized so that this does not become a bottleneck.
+ * The consequence is that on retrieving values, the operations that are
+ * currently being done won't be counted.
+ */
+public class StatisticListenerImpl implements CacheMapAccessEventListener,
+        CacheEntryEventListener, ScopeEventListener {
+
+    /**
+     * Hit counter.
+     */
+    private static int hitCount = 0;
+
+    /**
+     * Miss counter.
+     */
+    private static int missCount = 0;
+
+    /**
+     * Stale hit counter.
+     */
+    private static int staleHitCount = 0;
+
+    /**
+     * Hit counter sum.
+     */
+    private static int hitCountSum = 0;
+
+    /**
+     * Miss counter sum.
+     */
+    private static int missCountSum = 0;
+
+    /**
+     * Stale hit counter.
+     */
+    private static int staleHitCountSum = 0;
+
+    /**
+     * Flush hit counter.
+     */
+    private static int flushCount = 0;
+    
+    /**
+     * Miss counter sum.
+     */
+    private static int entriesAdded = 0;
+
+    /**
+     * Stale hit counter.
+     */
+    private static int entriesRemoved = 0;
+
+    /**
+     * Flush hit counter.
+     */
+    private static int entriesUpdated = 0;
+
+    /**
+     * Constructor, empty for us.
+     */
+    public StatisticListenerImpl() {
+
+    }
+
+    /**
+     * This method handles an event each time the cache is accessed.
+     * 
+     * @param event
+     *            The event triggered when the cache was accessed
+     * @see com.opensymphony.oscache.base.events.CacheMapAccessEventListener#accessed(CacheMapAccessEvent)
+     */
+    public void accessed(CacheMapAccessEvent event) {
+        // Retrieve the event type and update the counters
+        CacheMapAccessEventType type = event.getEventType();
+
+        // Handles a hit event
+        if (type == CacheMapAccessEventType.HIT) {
+            hitCount++;
+        } else if (type == CacheMapAccessEventType.STALE_HIT) { // Handles a
+                                                                // stale hit
+                                                                // event
+            staleHitCount++;
+        } else if (type == CacheMapAccessEventType.MISS) { // Handles a miss
+                                                            // event
+            missCount++;
+        }
+    }
+
+    /**
+     * Logs the flush of the cache.
+     * 
+     * @param info the string to be logged.
+     */
+    private void flushed(String info) {
+        flushCount++;
+
+        hitCountSum += hitCount;
+        staleHitCountSum += staleHitCount;
+        missCountSum += missCount;
+
+        hitCount = 0;
+        staleHitCount = 0;
+        missCount = 0;
+    }
+
+    /**
+     * Event fired when a specific or all scopes are flushed.
+     * 
+     * @param event ScopeEvent
+     * @see com.opensymphony.oscache.base.events.ScopeEventListener#scopeFlushed(ScopeEvent)
+     */
+    public void scopeFlushed(ScopeEvent event) {
+        flushed("scope " + ScopeEventListenerImpl.SCOPE_NAMES[event.getScope()]);
+    }
+
+    /**
+     * Event fired when an entry is added to the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryAdded(CacheEntryEvent)
+     */
+    public void cacheEntryAdded(CacheEntryEvent event) {
+        entriesAdded++;
+    }
+
+    /**
+     * Event fired when an entry is flushed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryFlushed(CacheEntryEvent)
+     */
+    public void cacheEntryFlushed(CacheEntryEvent event) {
+        // do nothing, because a group or other flush is coming
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin())) {
+            flushed("entry " + event.getKey() + " / " + event.getOrigin());
+        }
+    }
+
+    /**
+     * Event fired when an entry is removed from the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryRemoved(CacheEntryEvent)
+     */
+    public void cacheEntryRemoved(CacheEntryEvent event) {
+        entriesRemoved++;
+    }
+
+    /**
+     * Event fired when an entry is updated in the cache.
+     * 
+     * @param event CacheEntryEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheEntryUpdated(CacheEntryEvent)
+     */
+    public void cacheEntryUpdated(CacheEntryEvent event) {
+        entriesUpdated++;
+    }
+
+    /**
+     * Event fired when a group is flushed from the cache.
+     * 
+     * @param event CacheGroupEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheGroupFlushed(CacheGroupEvent)
+     */
+    public void cacheGroupFlushed(CacheGroupEvent event) {
+        flushed("group " + event.getGroup());
+    }
+
+    /**
+     * Event fired when a key pattern is flushed from the cache.
+     * 
+     * @param event CachePatternEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cachePatternFlushed(CachePatternEvent)
+     */
+    public void cachePatternFlushed(CachePatternEvent event) {
+        flushed("pattern " + event.getPattern());
+    }
+
+    /**
+     * An event that is fired when an entire cache gets flushed.
+     * 
+     * @param event CachewideEvent
+     * @see com.opensymphony.oscache.base.events.CacheEntryEventListener#cacheFlushed(CachewideEvent)
+     */
+    public void cacheFlushed(CachewideEvent event) {
+        flushed("wide " + event.getDate());
+    }
+
+    /**
+     * Return the counters in a string form.
+     * 
+     * @return String
+     */
+    public String toString() {
+        return "StatisticListenerImpl: Hit = " + hitCount + " / " + hitCountSum
+                + ", stale hit = " + staleHitCount + " / " + staleHitCountSum
+                + ", miss = " + missCount + " / " + missCountSum + ", flush = "
+                + flushCount + ", entries (added, removed, updates) = " 
+                + entriesAdded + ", " + entriesRemoved + ", " + entriesUpdated;
+    }
+
+    /**
+     * @return Returns the entriesAdded.
+     */
+    public int getEntriesAdded() {
+        return entriesAdded;
+    }
+
+    /**
+     * @return Returns the entriesRemoved.
+     */
+    public int getEntriesRemoved() {
+        return entriesRemoved;
+    }
+
+    /**
+     * @return Returns the entriesUpdated.
+     */
+    public int getEntriesUpdated() {
+        return entriesUpdated;
+    }
+
+    /**
+     * @return Returns the flushCount.
+     */
+    public int getFlushCount() {
+        return flushCount;
+    }
+
+    /**
+     * @return Returns the hitCount.
+     */
+    public int getHitCount() {
+        return hitCount;
+    }
+
+    /**
+     * @return Returns the hitCountSum.
+     */
+    public int getHitCountSum() {
+        return hitCountSum;
+    }
+
+    /**
+     * @return Returns the missCount.
+     */
+    public int getMissCount() {
+        return missCount;
+    }
+
+    /**
+     * @return Returns the missCountSum.
+     */
+    public int getMissCountSum() {
+        return missCountSum;
+    }
+
+    /**
+     * @return Returns the staleHitCount.
+     */
+    public int getStaleHitCount() {
+        return staleHitCount;
+    }
+
+    /**
+     * @return Returns the staleHitCountSum.
+     */
+    public int getStaleHitCountSum() {
+        return staleHitCountSum;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/extra/package.html b/src/java/com/opensymphony/oscache/extra/package.html
new file mode 100644
index 0000000..81ee366
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/extra/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides some basic event handler implementations that aren't essential to the core
+OSCache code, but form a useful starting point for basic logging or further development.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/general/GeneralCacheAdministrator.java b/src/java/com/opensymphony/oscache/general/GeneralCacheAdministrator.java
new file mode 100644
index 0000000..7526788
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/general/GeneralCacheAdministrator.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.general;
+
+import com.opensymphony.oscache.base.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Date;
+import java.util.Properties;
+
+/**
+ * A GeneralCacheAdministrator creates, flushes and administers the cache.
+ *
+ * EXAMPLES :
+ * <pre><code>
+ * // ---------------------------------------------------------------
+ * // Typical use with fail over
+ * // ---------------------------------------------------------------
+ * String myKey = "myKey";
+ * String myValue;
+ * int myRefreshPeriod = 1000;
+ * try {
+ *     // Get from the cache
+ *     myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);
+ * } catch (NeedsRefreshException nre) {
+ *     try {
+ *         // Get the value (probably by calling an EJB)
+ *         myValue = "This is the content retrieved.";
+ *         // Store in the cache
+ *         admin.putInCache(myKey, myValue);
+ *     } catch (Exception ex) {
+ *         // We have the current content if we want fail-over.
+ *         myValue = (String) nre.getCacheContent();
+ *         // It is essential that cancelUpdate is called if the
+ *         // cached content is not rebuilt
+ *         admin.cancelUpdate(myKey);
+ *     }
+ * }
+ *
+ *
+ *
+ * // ---------------------------------------------------------------
+ * // Typical use without fail over
+ * // ---------------------------------------------------------------
+ * String myKey = "myKey";
+ * String myValue;
+ * int myRefreshPeriod = 1000;
+ * try {
+ *     // Get from the cache
+ *     myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);
+ * } catch (NeedsRefreshException nre) {
+ *     try {
+ *         // Get the value (probably by calling an EJB)
+ *         myValue = "This is the content retrieved.";
+ *         // Store in the cache
+ *         admin.putInCache(myKey, myValue);
+ *         updated = true;
+ *     } finally {
+ *         if (!updated) {
+ *             // It is essential that cancelUpdate is called if the
+ *             // cached content could not be rebuilt
+ *             admin.cancelUpdate(myKey);
+ *         }
+ *     }
+ * }
+ * // ---------------------------------------------------------------
+ * // ---------------------------------------------------------------
+ * </code></pre>
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class GeneralCacheAdministrator extends AbstractCacheAdministrator {
+    private static transient final Log log = LogFactory.getLog(GeneralCacheAdministrator.class);
+
+    /**
+     * Application cache
+     */
+    private Cache applicationCache = null;
+
+    /**
+     * Create the cache administrator.
+     */
+    public GeneralCacheAdministrator() {
+        this(null);
+    }
+
+    /**
+     * Create the cache administrator with the specified properties
+     */
+    public GeneralCacheAdministrator(Properties p) {
+        super(p);
+        log.info("Constructed GeneralCacheAdministrator()");
+        createCache();
+    }
+
+    /**
+     * Grabs a cache
+     *
+     * @return The cache
+     */
+    public Cache getCache() {
+        return applicationCache;
+    }
+    
+    /**
+     * Remove an object from the cache
+     *
+     * @param key             The key entered by the user.
+     */
+    public void removeEntry(String key) {
+        getCache().removeEntry(key);
+    }
+    /**
+     * Get an object from the cache
+     *
+     * @param key             The key entered by the user.
+     * @return   The object from cache
+     * @throws NeedsRefreshException when no cache entry could be found with the
+     * supplied key, or when an entry was found but is considered out of date. If
+     * the cache entry is a new entry that is currently being constructed this method
+     * will block until the new entry becomes available. Similarly, it will block if
+     * a stale entry is currently being rebuilt by another thread and cache blocking is
+     * enabled (<code>cache.blocking=true</code>).
+     */
+    public Object getFromCache(String key) throws NeedsRefreshException {
+        return getCache().getFromCache(key);
+    }
+
+    /**
+     * Get an object from the cache
+     *
+     * @param key             The key entered by the user.
+     * @param refreshPeriod   How long the object can stay in cache in seconds. To
+     * allow the entry to stay in the cache indefinitely, supply a value of
+     * {@link CacheEntry#INDEFINITE_EXPIRY}
+     * @return   The object from cache
+     * @throws NeedsRefreshException when no cache entry could be found with the
+     * supplied key, or when an entry was found but is considered out of date. If
+     * the cache entry is a new entry that is currently being constructed this method
+     * will block until the new entry becomes available. Similarly, it will block if
+     * a stale entry is currently being rebuilt by another thread and cache blocking is
+     * enabled (<code>cache.blocking=true</code>).
+     */
+    public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
+        return getCache().getFromCache(key, refreshPeriod);
+    }
+
+    /**
+     * Get an object from the cache
+     *
+     * @param key             The key entered by the user.
+     * @param refreshPeriod   How long the object can stay in cache in seconds. To
+     * allow the entry to stay in the cache indefinitely, supply a value of
+     * {@link CacheEntry#INDEFINITE_EXPIRY}
+     * @param cronExpression  A cron expression that the age of the cache entry
+     * will be compared to. If the entry is older than the most recent match for the
+     * cron expression, the entry will be considered stale.
+     * @return   The object from cache
+     * @throws NeedsRefreshException when no cache entry could be found with the
+     * supplied key, or when an entry was found but is considered out of date. If
+     * the cache entry is a new entry that is currently being constructed this method
+     * will block until the new entry becomes available. Similarly, it will block if
+     * a stale entry is currently being rebuilt by another thread and cache blocking is
+     * enabled (<code>cache.blocking=true</code>).
+     */
+    public Object getFromCache(String key, int refreshPeriod, String cronExpression) throws NeedsRefreshException {
+        return getCache().getFromCache(key, refreshPeriod, cronExpression);
+    }
+
+    /**
+     * Cancels a pending cache update. This should only be called by a thread
+     * that received a {@link NeedsRefreshException} and was unable to generate
+     * some new cache content.
+     *
+     * @param key The cache entry key to cancel the update of.
+     */
+    public void cancelUpdate(String key) {
+        getCache().cancelUpdate(key);
+    }
+
+    /**
+     * Shuts down the cache administrator.
+     */
+    public void destroy() {
+        finalizeListeners(applicationCache);
+    }
+
+    // METHODS THAT DELEGATES TO THE CACHE ---------------------
+
+    /**
+     * Flush the entire cache immediately.
+     */
+    public void flushAll() {
+        getCache().flushAll(new Date());
+    }
+
+    /**
+     * Flush the entire cache at the given date.
+     *
+     * @param date The time to flush
+     */
+    public void flushAll(Date date) {
+        getCache().flushAll(date);
+    }
+
+    /**
+     * Flushes a single cache entry.
+     */
+    public void flushEntry(String key) {
+        getCache().flushEntry(key);
+    }
+
+    /**
+     * Flushes all items that belong to the specified group.
+     *
+     * @param group The name of the group to flush
+     */
+    public void flushGroup(String group) {
+        getCache().flushGroup(group);
+    }
+
+    /**
+     * Allows to flush all items that have a specified pattern in the key.
+     *
+     * @param pattern     Pattern.
+     * @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) {
+        getCache().flushPattern(pattern);
+    }
+
+    /**
+     * Put an object in a cache
+     *
+     * @param key       The key entered by the user
+     * @param content   The object to store
+     * @param policy    Object that implements refresh policy logic
+     */
+    public void putInCache(String key, Object content, EntryRefreshPolicy policy) {
+        Cache cache = getCache();
+        cache.putInCache(key, content, policy);
+    }
+
+    /**
+     * Put an object in a cache
+     *
+     * @param key       The key entered by the user
+     * @param content   The object to store
+     */
+    public void putInCache(String key, Object content) {
+        putInCache(key, content, (EntryRefreshPolicy) null);
+    }
+
+    /**
+     * Puts an object in a cache
+     *
+     * @param key      The unique key for this cached object
+     * @param content  The object to store
+     * @param groups   The groups that this object belongs to
+     */
+    public void putInCache(String key, Object content, String[] groups) {
+        getCache().putInCache(key, content, groups);
+    }
+
+    /**
+     * Puts an object in a cache
+     *
+     * @param key      The unique key for this cached object
+     * @param content  The object to store
+     * @param groups   The groups that this object belongs to
+     * @param policy   The refresh policy to use
+     */
+    public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy) {
+        getCache().putInCache(key, content, groups, policy, null);
+    }
+
+    /**
+     * Sets the cache capacity (number of items). If the cache contains
+     * more than <code>capacity</code> items then items will be removed
+     * to bring the cache back down to the new size.
+     *
+     * @param capacity The new capacity of the cache
+     */
+    public void setCacheCapacity(int capacity) {
+        super.setCacheCapacity(capacity);
+        getCache().setCapacity(capacity);
+    }
+
+    /**
+     * Creates a cache in this admin
+     */
+    private void createCache() {
+        log.info("Creating new cache");
+
+        applicationCache = new Cache(isMemoryCaching(), isUnlimitedDiskCache(), isOverflowPersistence(), isBlocking(), algorithmClass, cacheCapacity);
+
+        configureStandardListeners(applicationCache);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/general/package.html b/src/java/com/opensymphony/oscache/general/package.html
new file mode 100644
index 0000000..fd122ac
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/general/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides a generic administrator class for the cache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/hibernate/OSCache.java b/src/java/com/opensymphony/oscache/hibernate/OSCache.java
new file mode 100644
index 0000000..19c9f60
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/hibernate/OSCache.java
@@ -0,0 +1,161 @@
+package com.opensymphony.oscache.hibernate;
+
+import java.util.Map;
+
+import org.hibernate.cache.Cache;
+import org.hibernate.cache.CacheException;
+import org.hibernate.cache.Timestamper;
+
+import com.opensymphony.oscache.base.NeedsRefreshException;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+/**
+ * Cache plugin for Hibernate 3.2 and OpenSymphony OSCache 2.4.
+ * <p/>
+ * The OSCache implementation assumes that identifiers have well-behaved <tt>toString()</tt> methods.
+ * This implementation <b>must</b> be threadsafe.
+ * 
+ * @version $Revision:$
+ */
+public class OSCache implements Cache {
+    
+    /** The OSCache 2.4 cache administrator. */
+    private GeneralCacheAdministrator cache;
+    private final int refreshPeriod;
+    private final String cron;
+    private final String regionName;
+    private final String[] regionGroups;
+    
+    public OSCache(GeneralCacheAdministrator cache, int refreshPeriod, String cron, String region) {
+        this.cache = cache;
+        this.refreshPeriod = refreshPeriod;
+        this.cron = cron;
+        this.regionName = region;
+        this.regionGroups = new String[] {region};
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#get(java.lang.Object)
+     */
+    public Object get(Object key) throws CacheException {
+        try {
+            return cache.getFromCache( toString(key), refreshPeriod, cron );
+        }
+        catch (NeedsRefreshException e) {
+            cache.cancelUpdate( toString(key) );
+            return null;
+        }
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#put(java.lang.Object, java.lang.Object)
+     */
+    public void put(Object key, Object value) throws CacheException {
+        cache.putInCache( toString(key), value, regionGroups );
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#remove(java.lang.Object)
+     */
+    public void remove(Object key) throws CacheException {
+        cache.flushEntry( toString(key) );
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#clear()
+     */
+    public void clear() throws CacheException {
+        cache.flushGroup(regionName);
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#destroy()
+     */
+    public void destroy() throws CacheException {
+        synchronized (cache) {
+            cache.destroy();
+        }
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#lock(java.lang.Object)
+     */
+    public void lock(Object key) throws CacheException {
+        // local cache, so we use synchronization
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#unlock(java.lang.Object)
+     */
+    public void unlock(Object key) throws CacheException {
+        // local cache, so we use synchronization
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#nextTimestamp()
+     */
+    public long nextTimestamp() {
+        return Timestamper.next();
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#getTimeout()
+     */
+    public int getTimeout() {
+        return Timestamper.ONE_MS * 60000; //ie. 60 seconds
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#toMap()
+     */
+    public Map toMap() {
+        throw new UnsupportedOperationException();
+    }    
+
+    /**
+     * @see org.hibernate.cache.Cache#getElementCountOnDisk()
+     */
+    public long getElementCountOnDisk() {
+        return -1;
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#getElementCountInMemory()
+     */
+    public long getElementCountInMemory() {
+        return -1;
+    }
+    
+    /**
+     * @see org.hibernate.cache.Cache#getSizeInMemory()
+     */
+    public long getSizeInMemory() {
+        return -1;
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#getRegionName()
+     */
+    public String getRegionName() {
+        return regionName;
+    }
+
+    /**
+     * @see org.hibernate.cache.Cache#update(java.lang.Object, java.lang.Object)
+     */
+    public void update(Object key, Object value) throws CacheException {
+        put(key, value);
+    }    
+
+    /**
+     * @see org.hibernate.cache.Cache#read(java.lang.Object)
+     */
+    public Object read(Object key) throws CacheException {
+        return get(key);
+    }
+    
+    private String toString(Object key) {
+        return String.valueOf(key) + "." + regionName;
+    }
+
+}
diff --git a/src/java/com/opensymphony/oscache/hibernate/OSCacheProvider.java b/src/java/com/opensymphony/oscache/hibernate/OSCacheProvider.java
new file mode 100644
index 0000000..3993e8d
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/hibernate/OSCacheProvider.java
@@ -0,0 +1,123 @@
+package com.opensymphony.oscache.hibernate;
+
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.hibernate.cache.Cache;
+import org.hibernate.cache.CacheException;
+import org.hibernate.cache.CacheProvider;
+import org.hibernate.cache.Timestamper;
+import org.hibernate.util.StringHelper;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+import com.opensymphony.oscache.util.StringUtil;
+
+/**
+ * Cache provider plugin for Hibernate 3.2 and OpenSymphony OSCache 2.4.
+ * <p/>
+ * This implementation assumes that identifiers have well-behaved <tt>toString()</tt> methods.
+ * <p/>
+ * To enable OSCache for Hibernate's second level cache add the following line to Hibernate's configuration e.g. <code>hibernate.cfg.xml</code>):
+ * <code>hibernate.cache.provider_class=com.opensymphony.oscache.hibernate.OSCacheProvider</code>
+ * To configure a different configuration file use the following parameter in the Hibernate's configuration:
+ * <code>com.opensymphony.oscache.configurationResourceName=[path to oscache-hibernate.properties]</code>
+ * 
+ * @version $Revision:$
+ */
+public class OSCacheProvider implements CacheProvider {
+
+    private static final Log LOG = LogFactory.getLog(OSCacheProvider.class);
+
+    /** In the Hibernate system property you can specify the location of the oscache configuration file name. */
+    public static final String OSCACHE_CONFIGURATION_RESOURCE_NAME = "com.opensymphony.oscache.configurationResourceName";
+
+    /** The <tt>OSCache</tt> refresh period property suffix. */
+    public static final String OSCACHE_REFRESH_PERIOD = "refresh.period";
+
+    /** The <tt>OSCache</tt> CRON expression property suffix. */
+    public static final String OSCACHE_CRON = "cron";
+    
+    private static GeneralCacheAdministrator cache;
+
+    /**
+     * Builds a new {@link Cache} instance, and gets it's properties from the 
+     * GeneralCacheAdministrator {@link GeneralCacheAdministrator}
+     * which reads the properties file (<code>oscache.properties</code>) in the start method: 
+     * @see com.opensymphony.oscache.hibernate.OSCacheProvider#start(java.util.Properties)
+     *
+     * @param region the region of the cache
+     * @param properties not used
+     * @return the hibernate 2nd level cache
+     * @throws CacheException
+     * 
+     * @see org.hibernate.cache.CacheProvider#buildCache(java.lang.String, java.util.Properties)
+     */
+    public Cache buildCache(String region, Properties properties) throws CacheException {
+        if (cache != null) {
+            LOG.debug("building cache in OSCacheProvider...");
+            
+            String refreshPeriodString = cache.getProperty( StringHelper.qualify(region, OSCACHE_REFRESH_PERIOD) );
+            int refreshPeriod = refreshPeriodString==null ? CacheEntry.INDEFINITE_EXPIRY : Integer.parseInt( refreshPeriodString.trim() );
+            
+            String cron = cache.getProperty( StringHelper.qualify(region, OSCACHE_CRON) );
+            
+            return new OSCache(cache, refreshPeriod, cron, region);
+        }
+        throw new CacheException("OSCache was stopped or wasn't configured via method start.");
+    }
+
+    /**
+     * @see org.hibernate.cache.CacheProvider#nextTimestamp()
+     */
+    public long nextTimestamp() {
+        return Timestamper.next();
+    }
+
+    /**
+     * This method isn't documented in Hibernate:
+     * @see org.hibernate.cache.CacheProvider#isMinimalPutsEnabledByDefault()
+     */
+    public boolean isMinimalPutsEnabledByDefault() {
+        return false;
+    }
+
+    /**
+     * @see org.hibernate.cache.CacheProvider#stop()
+     */
+    public void stop() {
+        if (cache != null) {
+            LOG.debug("Stopping OSCacheProvider...");
+            cache.destroy();
+            cache = null;
+            LOG.debug("OSCacheProvider stopped.");
+        }
+    }
+
+    /**
+     * @see org.hibernate.cache.CacheProvider#start(java.util.Properties)
+     */
+    public void start(Properties hibernateSystemProperties) throws CacheException {
+        if (cache == null) {
+            // construct the cache
+            LOG.debug("Starting OSCacheProvider...");
+            String configResourceName = null;
+            if (hibernateSystemProperties != null) {
+                configResourceName = (String) hibernateSystemProperties.get(OSCACHE_CONFIGURATION_RESOURCE_NAME);
+            }
+            if (StringUtil.isEmpty(configResourceName)) {
+                cache = new GeneralCacheAdministrator();
+            } else {
+                Properties propertiesOSCache = Config.loadProperties(configResourceName, this.getClass().getName());
+                cache = new GeneralCacheAdministrator(propertiesOSCache);
+            }
+            LOG.debug("OSCacheProvider started.");
+        } else {
+            LOG.warn("Tried to restart OSCacheProvider, which is already running.");
+        }
+    }
+    
+}
diff --git a/src/java/com/opensymphony/oscache/hibernate/package.html b/src/java/com/opensymphony/oscache/hibernate/package.html
new file mode 100644
index 0000000..973f1fc
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/hibernate/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2007 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides Hibernate 3.2 classes for OSCache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/AbstractBroadcastingListener.java b/src/java/com/opensymphony/oscache/plugins/clustersupport/AbstractBroadcastingListener.java
new file mode 100644
index 0000000..3a215b7
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/AbstractBroadcastingListener.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.*;
+import com.opensymphony.oscache.base.events.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Date;
+
+/**
+ * Implementation of a CacheEntryEventListener. It broadcasts the flush events
+ * across a cluster to other listening caches. Note that this listener cannot
+ * be used in conjection with session caches.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public abstract class AbstractBroadcastingListener implements CacheEntryEventListener, LifecycleAware {
+    private final static Log log = LogFactory.getLog(AbstractBroadcastingListener.class);
+
+    /**
+     * The name to use for the origin of cluster events. Using this ensures
+     * events are not fired recursively back over the cluster.
+     */
+    protected static final String CLUSTER_ORIGIN = "CLUSTER";
+    protected Cache cache = null;
+
+    public AbstractBroadcastingListener() {
+        if (log.isInfoEnabled()) {
+            log.info("AbstractBroadcastingListener registered");
+        }
+    }
+
+    /**
+     * Event fired when an entry is flushed from the cache. This broadcasts
+     * the flush message to any listening nodes on the network.
+     */
+    public void cacheEntryFlushed(CacheEntryEvent event) {
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
+            if (log.isDebugEnabled()) {
+                log.debug("cacheEntryFlushed called (" + event + ")");
+            }
+
+            sendNotification(new ClusterNotification(ClusterNotification.FLUSH_KEY, event.getKey()));
+        }
+    }
+
+    /**
+     * Event fired when an entry is removed from the cache. This broadcasts
+     * the remove method to any listening nodes on the network, as long as
+     * this event wasn't from a broadcast in the first place.
+     */
+    public void cacheGroupFlushed(CacheGroupEvent event) {
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
+            if (log.isDebugEnabled()) {
+                log.debug("cacheGroupFushed called (" + event + ")");
+            }
+
+            sendNotification(new ClusterNotification(ClusterNotification.FLUSH_GROUP, event.getGroup()));
+        }
+    }
+
+    public void cachePatternFlushed(CachePatternEvent event) {
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
+            if (log.isDebugEnabled()) {
+                log.debug("cachePatternFushed called (" + event + ")");
+            }
+
+            sendNotification(new ClusterNotification(ClusterNotification.FLUSH_PATTERN, event.getPattern()));
+        }
+    }
+
+    public void cacheFlushed(CachewideEvent event) {
+        if (!Cache.NESTED_EVENT.equals(event.getOrigin()) && !CLUSTER_ORIGIN.equals(event.getOrigin())) {
+            if (log.isDebugEnabled()) {
+                log.debug("cacheFushed called (" + event + ")");
+            }
+
+            sendNotification(new ClusterNotification(ClusterNotification.FLUSH_CACHE, event.getDate()));
+        }
+    }
+
+    // --------------------------------------------------------
+    // The remaining events are of no interest to this listener
+    // --------------------------------------------------------
+    public void cacheEntryAdded(CacheEntryEvent event) {
+    }
+
+    public void cacheEntryRemoved(CacheEntryEvent event) {
+    }
+
+    public void cacheEntryUpdated(CacheEntryEvent event) {
+    }
+
+    public void cacheGroupAdded(CacheGroupEvent event) {
+    }
+
+    public void cacheGroupEntryAdded(CacheGroupEvent event) {
+    }
+
+    public void cacheGroupEntryRemoved(CacheGroupEvent event) {
+    }
+
+    public void cacheGroupRemoved(CacheGroupEvent event) {
+    }
+
+    public void cacheGroupUpdated(CacheGroupEvent event) {
+    }
+
+    /**
+     * Called by the cache administrator class when a cache is instantiated.
+     *
+     * @param cache the cache instance that this listener is attached to.
+     * @param config The cache's configuration details. This allows the event handler
+     * to initialize itself based on the cache settings, and also to receive <em>additional</em>
+     * settings that were part of the cache configuration but that the cache
+     * itself does not care about. If you are using <code>cache.properties</code>
+     * for your configuration, simply add any additional properties that your event
+     * handler requires and they will be passed through in this parameter.
+     *
+     * @throws InitializationException thrown when there was a problem initializing the
+     * listener. The cache administrator will log this error and disable the listener.
+     */
+    public void initialize(Cache cache, Config config) throws InitializationException {
+        this.cache = cache;
+    }
+
+    /**
+     * Handles incoming notification messages. This method should be called by the
+     * underlying broadcasting implementation when a message is received from another
+     * node in the cluster.
+     *
+     * @param message The incoming cluster notification message object.
+     */
+    public void handleClusterNotification(ClusterNotification message) {
+        if (cache == null) {
+            log.warn("A cluster notification (" + message + ") was received, but no cache is registered on this machine. Notification ignored.");
+
+            return;
+        }
+
+        if (log.isInfoEnabled()) {
+            log.info("Cluster notification (" + message + ") was received.");
+        }
+
+        switch (message.getType()) {
+            case ClusterNotification.FLUSH_KEY:
+                cache.flushEntry((String) message.getData(), CLUSTER_ORIGIN);
+                break;
+            case ClusterNotification.FLUSH_GROUP:
+                cache.flushGroup((String) message.getData(), CLUSTER_ORIGIN);
+                break;
+            case ClusterNotification.FLUSH_PATTERN:
+                cache.flushPattern((String) message.getData(), CLUSTER_ORIGIN);
+                break;
+            case ClusterNotification.FLUSH_CACHE:
+                cache.flushAll((Date) message.getData(), CLUSTER_ORIGIN);
+                break;
+            default:
+                log.error("The cluster notification (" + message + ") is of an unknown type. Notification ignored.");
+        }
+    }
+
+    /**
+     * Called when a cluster notification message is to be broadcast. Implementing
+     * classes should use their underlying transport to broadcast the message across
+     * the cluster.
+     *
+     * @param message The notification message to broadcast.
+     */
+    abstract protected void sendNotification(ClusterNotification message);
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/ClusterNotification.java b/src/java/com/opensymphony/oscache/plugins/clustersupport/ClusterNotification.java
new file mode 100644
index 0000000..8a9f9ec
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/ClusterNotification.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import java.io.Serializable;
+
+/**
+ * A notification message that holds information about a cache event. This
+ * class is <code>Serializable</code> to allow it to be sent across the
+ * network to other machines running in a cluster.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @author $Author: dres $
+ * @version $Revision: 254 $
+ */
+public class ClusterNotification implements Serializable {
+    /**
+     * Specifies a notification message that indicates a particular cache key
+     * should be flushed.
+     */
+    public static final int FLUSH_KEY = 1;
+
+    /**
+     * Specifies a notification message that indicates an entire cache group
+     * should be flushed.
+     */
+    public static final int FLUSH_GROUP = 2;
+
+    /**
+     * Specifies a notification message that indicates all entries in the cache
+     * that match the specified pattern should be flushed.
+     */
+    public static final int FLUSH_PATTERN = 3;
+
+    /**
+     * Specifies a notification message indicating that an entire cache should
+     * be flushed.
+     */
+    public static final int FLUSH_CACHE = 4;
+
+    /**
+     * Any additional data that may be required
+     */
+    protected Serializable data;
+
+    /**
+     * The type of notification message.
+     */
+    protected int type;
+
+    /**
+     * Creates a new notification message object to broadcast to other
+     * listening nodes in the cluster.
+     *
+     * @param type       The type of notification message. Valid types are
+     *                   {@link #FLUSH_KEY} and {@link #FLUSH_GROUP}.
+     * @param data       Specifies the object key or group name to flush.
+     */
+    public ClusterNotification(int type, Serializable data) {
+        this.type = type;
+        this.data = data;
+    }
+
+    /**
+     * Holds any additional data that was required
+     */
+    public Serializable getData() {
+        return data;
+    }
+
+    /**
+     * The type of notification message.
+     */
+    public int getType() {
+        return type;
+    }
+
+    public String toString() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("type=").append(type).append(", data=").append(data);
+
+        return buf.toString();
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/JMS10BroadcastingListener.java b/src/java/com/opensymphony/oscache/plugins/clustersupport/JMS10BroadcastingListener.java
new file mode 100644
index 0000000..1b34819
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/JMS10BroadcastingListener.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.FinalizationException;
+import com.opensymphony.oscache.base.InitializationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.jms.*;
+
+import javax.naming.InitialContext;
+
+/**
+ * A JMS 1.0.x based clustering implementation. This implementation is independent of
+ * the JMS provider and uses non-persistent messages on a publish subscribe protocol.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class JMS10BroadcastingListener extends AbstractBroadcastingListener {
+    private final static Log log = LogFactory.getLog(JMS10BroadcastingListener.class);
+
+    /**
+     * The name of this cluster. Used to identify the sender of a message.
+     */
+    private String clusterNode;
+
+    /**
+     *The JMS connection used
+     */
+    private TopicConnection connection;
+
+    /**
+     * Th object used to publish new messages
+     */
+    private TopicPublisher publisher;
+
+    /**
+     * The current JMS session
+     */
+    private TopicSession publisherSession;
+
+    /**
+     * <p>Called by the cache administrator class when a cache is instantiated.</p>
+     * <p>The JMS broadcasting implementation requires the following configuration
+     * properties to be specified in <code>oscache.properties</code>:
+     * <ul>
+     * <li><b>cache.cluster.jms.topic.factory</b> - The JMS connection factory to use</li>
+     * <li><b>cache.cluster.jms.topic.name</b> - The JMS topic name</li>
+     * <li><b>cache.cluster.jms.node.name</b> - The name of this node in the cluster. This should
+     * be unique for each node.</li>
+     * Please refer to the clustering documentation for further details on configuring
+     * the JMS clustered caching.</p>
+     *
+     * @param cache the cache instance that this listener is attached to.
+     *
+     * @throws InitializationException thrown when there was a
+     * problem initializing the listener. The cache administrator will log this error and
+     * disable the listener.
+     */
+    public void initialize(Cache cache, Config config) throws InitializationException {
+        super.initialize(cache, config);
+
+        // Get the name of this node
+        clusterNode = config.getProperty("cache.cluster.jms.node.name");
+
+        String topic = config.getProperty("cache.cluster.jms.topic.name");
+        String topicFactory = config.getProperty("cache.cluster.jms.topic.factory");
+
+        if (log.isInfoEnabled()) {
+            log.info("Starting JMS clustering (node name=" + clusterNode + ", topic=" + topic + ", topic factory=" + topicFactory + ")");
+        }
+
+        try {
+            // Make sure you have specified the necessary JNDI properties (usually in
+            // a jndi.properties resource file, or as system properties)
+            InitialContext jndi = new InitialContext();
+
+            // Look up a JMS connection factory
+            TopicConnectionFactory connectionFactory = (TopicConnectionFactory) jndi.lookup(topicFactory);
+
+            // Create a JMS connection
+            connection = connectionFactory.createTopicConnection();
+
+            // Create session objects
+            publisherSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            TopicSession subSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            // Look up the JMS topic
+            Topic chatTopic = (Topic) jndi.lookup(topic);
+
+            // Create the publisher and subscriber
+            publisher = publisherSession.createPublisher(chatTopic);
+
+            TopicSubscriber subscriber = subSession.createSubscriber(chatTopic);
+
+            // Set the message listener
+            subscriber.setMessageListener(new MessageListener() {
+                    public void onMessage(Message message) {
+                        try {
+                            //check the message type
+                            ObjectMessage objectMessage = null;
+
+                            if (!(message instanceof ObjectMessage)) {
+                                log.error("Cannot handle message of type (class=" + message.getClass().getName() + "). Notification ignored.");
+                                return;
+                            }
+
+                            objectMessage = (ObjectMessage) message;
+
+                            //check the message content
+                            if (!(objectMessage.getObject() instanceof ClusterNotification)) {
+                                log.error("An unknown cluster notification message received (class=" + objectMessage.getObject().getClass().getName() + "). Notification ignored.");
+                                return;
+                            }
+
+                            if (log.isDebugEnabled()) {
+                                log.debug(objectMessage.getObject());
+                            }
+
+                            // This prevents the notification sent by this node from being handled by itself
+                            if (!objectMessage.getStringProperty("nodeName").equals(clusterNode)) {
+                                //now handle the message
+                                ClusterNotification notification = (ClusterNotification) objectMessage.getObject();
+                                handleClusterNotification(notification);
+                            }
+                        } catch (JMSException jmsEx) {
+                            log.error("Cannot handle cluster Notification", jmsEx);
+                        }
+                    }
+                });
+
+            // Start the JMS connection; allows messages to be delivered
+            connection.start();
+        } catch (Exception e) {
+            throw new InitializationException("Initialization of the JMS10BroadcastingListener failed: " + e);
+        }
+    }
+
+    /**
+     * Called by the cache administrator class when a cache is destroyed.
+     *
+     * @throws FinalizationException thrown when there was a problem finalizing the
+     * listener. The cache administrator will catch and log this error.
+     */
+    public void finialize() throws FinalizationException {
+        try {
+            if (log.isInfoEnabled()) {
+                log.info("Shutting down JMS clustering...");
+            }
+
+            connection.close();
+
+            if (log.isInfoEnabled()) {
+                log.info("JMS clustering shutdown complete.");
+            }
+        } catch (JMSException e) {
+            log.warn("A problem was encountered when closing the JMS connection", e);
+        }
+    }
+
+    protected void sendNotification(ClusterNotification message) {
+        try {
+            ObjectMessage objectMessage = publisherSession.createObjectMessage();
+            objectMessage.setObject(message);
+
+            //sign the message, with the name of this node
+            objectMessage.setStringProperty("nodeName", clusterNode);
+            publisher.publish(objectMessage);
+        } catch (JMSException e) {
+            log.error("Cannot send notification " + message, e);
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/JMSBroadcastingListener.java b/src/java/com/opensymphony/oscache/plugins/clustersupport/JMSBroadcastingListener.java
new file mode 100644
index 0000000..3433627
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/JMSBroadcastingListener.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.FinalizationException;
+import com.opensymphony.oscache.base.InitializationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.jms.*;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+/**
+ * A JMS based clustering implementation. This implementation is independent of the
+ * JMS provider and uses non-persistent messages on a publish subscribe protocol.
+ *
+ * @author <a href="mailto:motoras at linuxmail.org">Romulus Pasca</a>
+ */
+public class JMSBroadcastingListener extends AbstractBroadcastingListener {
+    
+    private final static Log log = LogFactory.getLog(JMSBroadcastingListener.class);
+
+    /**
+     *The JMS connection used
+     */
+    private Connection connection;
+
+    /**
+     * Th object used to publish new messages
+     */
+    private MessageProducer messagePublisher;
+
+    /**
+     * The current JMS session
+     */
+    private Session publisherSession;
+
+    /**
+     * The name of this cluster. Used to identify the sender of a message.
+     */
+    private String clusterNode;
+
+    /**
+     * <p>Called by the cache administrator class when a cache is instantiated.</p>
+     * <p>The JMS broadcasting implementation requires the following configuration
+     * properties to be specified in <code>oscache.properties</code>:
+     * <ul>
+     * <li><b>cache.cluster.jms.topic.factory</b> - The JMS connection factory to use</li>
+     * <li><b>cache.cluster.jms.topic.name</b> - The JMS topic name</li>
+     * <li><b>cache.cluster.jms.node.name</b> - The name of this node in the cluster. This
+     * should be unique for each node.</li>
+     * Please refer to the clustering documentation for further details on configuring
+     * the JMS clustered caching.</p>
+     *
+     * @param cache the cache instance that this listener is attached to.
+     *
+     * @throws com.opensymphony.oscache.base.InitializationException thrown when there was a
+     * problem initializing the listener. The cache administrator will log this error and
+     * disable the listener.
+     */
+    public void initialize(Cache cache, Config config) throws InitializationException {
+        super.initialize(cache, config);
+
+        // Get the name of this node
+        clusterNode = config.getProperty("cache.cluster.jms.node.name");
+
+        String topic = config.getProperty("cache.cluster.jms.topic.name");
+        String topicFactory = config.getProperty("cache.cluster.jms.topic.factory");
+
+        if (log.isInfoEnabled()) {
+            log.info("Starting JMS clustering (node name=" + clusterNode + ", topic=" + topic + ", topic factory=" + topicFactory + ")");
+        }
+
+        try {
+            // Make sure you have specified the necessary JNDI properties (usually in
+            // a jndi.properties resource file, or as system properties)
+            InitialContext jndi = getInitialContext();
+
+            // Look up a JMS connection factory
+            ConnectionFactory connectionFactory = (ConnectionFactory) jndi.lookup(topicFactory);
+
+            // Create a JMS connection
+            connection = connectionFactory.createConnection();
+
+            // Create session objects
+            publisherSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            Session subSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            // Look up the JMS topic
+            Topic chatTopic = (Topic) jndi.lookup(topic);
+
+            // Create the publisher and subscriber
+            messagePublisher = publisherSession.createProducer(chatTopic);
+
+            MessageConsumer messageConsumer = subSession.createConsumer(chatTopic);
+
+            // Set the message listener
+            messageConsumer.setMessageListener(new MessageListener() {
+                    public void onMessage(Message message) {
+                        try {
+                            //check the message type
+                            ObjectMessage objectMessage = null;
+
+                            if (!(message instanceof ObjectMessage)) {
+                                log.error("Cannot handle message of type (class=" + message.getClass().getName() + "). Notification ignored.");
+                                return;
+                            }
+
+                            objectMessage = (ObjectMessage) message;
+
+                            //check the message content
+                            if (!(objectMessage.getObject() instanceof ClusterNotification)) {
+                                log.error("An unknown cluster notification message received (class=" + objectMessage.getObject().getClass().getName() + "). Notification ignored.");
+                                return;
+                            }
+
+                            if (log.isDebugEnabled()) {
+                                log.debug(objectMessage.getObject());
+                            }
+
+                            // This prevents the notification sent by this node from being handled by itself
+                            if (!objectMessage.getStringProperty("nodeName").equals(clusterNode)) {
+                                //now handle the message
+                                ClusterNotification notification = (ClusterNotification) objectMessage.getObject();
+                                handleClusterNotification(notification);
+                            }
+                        } catch (JMSException jmsEx) {
+                            log.error("Cannot handle cluster Notification", jmsEx);
+                        }
+                    }
+                });
+
+            // Start the JMS connection; allows messages to be delivered
+            connection.start();
+        } catch (Exception e) {
+            throw new InitializationException("Initialization of the JMSBroadcastingListener failed: " + e);
+        }
+    }
+
+    /**
+     * Called by the cache administrator class when a cache is destroyed.
+     *
+     * @throws com.opensymphony.oscache.base.FinalizationException thrown when there was a problem finalizing the
+     * listener. The cache administrator will catch and log this error.
+     */
+    public void finialize() throws FinalizationException {
+        try {
+            if (log.isInfoEnabled()) {
+                log.info("Shutting down JMS clustering...");
+            }
+
+            connection.close();
+
+            if (log.isInfoEnabled()) {
+                log.info("JMS clustering shutdown complete.");
+            }
+        } catch (JMSException e) {
+            log.warn("A problem was encountered when closing the JMS connection", e);
+        }
+    }
+
+    protected void sendNotification(ClusterNotification message) {
+        try {
+            ObjectMessage objectMessage = publisherSession.createObjectMessage();
+            objectMessage.setObject(message);
+
+            //sign the message, with the name of this node
+            objectMessage.setStringProperty("nodeName", clusterNode);
+            messagePublisher.send(objectMessage);
+        } catch (JMSException e) {
+            log.error("Cannot send notification " + message, e);
+        }
+    }
+    
+    /**
+     * @return creates a context for performing naming operations.
+     * @throws NamingException if a naming exception is encountered
+     */
+    protected InitialContext getInitialContext() throws NamingException {
+        return new InitialContext();
+    }
+
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/JavaGroupsBroadcastingListener.java b/src/java/com/opensymphony/oscache/plugins/clustersupport/JavaGroupsBroadcastingListener.java
new file mode 100644
index 0000000..4d487a4
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/JavaGroupsBroadcastingListener.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.FinalizationException;
+import com.opensymphony.oscache.base.InitializationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.jgroups.Address;
+import org.jgroups.Channel;
+
+import org.jgroups.blocks.NotificationBus;
+
+import java.io.Serializable;
+
+/**
+ * <p>A concrete implementation of the {@link AbstractBroadcastingListener} based on
+ * the JavaGroups library. This Class uses JavaGroups to broadcast cache flush
+ * messages across a cluster.</p>
+ *
+ * <p>One of the following properties should be configured in <code>oscache.properties</code> for
+ * this listener:
+ * <ul>
+ * <li><b>cache.cluster.multicast.ip</b> - The multicast IP that JavaGroups should use for broadcasting</li>
+ * <li><b>cache.cluster.properties</b> - The JavaGroups channel properties to use. Allows for precise
+ * control over the behaviour of JavaGroups</li>
+ * </ul>
+ * Please refer to the clustering documentation for further details on the configuration of this listener.</p>
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class JavaGroupsBroadcastingListener extends AbstractBroadcastingListener implements NotificationBus.Consumer {
+    private final static Log log = LogFactory.getLog(JavaGroupsBroadcastingListener.class);
+    private static final String BUS_NAME = "OSCacheBus";
+    private static final String CHANNEL_PROPERTIES = "cache.cluster.properties";
+    private static final String MULTICAST_IP_PROPERTY = "cache.cluster.multicast.ip";
+
+    /**
+    * The first half of the default channel properties. They default channel properties are:
+    * <pre>
+    * UDP(mcast_addr=*.*.*.*;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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+    * UNICAST(timeout=300,600,1200,2400):\
+    * pbcast.STABLE(desired_avg_gossip=20000):\
+    * 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)
+    * </pre>
+    *
+    * Where <code>*.*.*.*</code> is the specified multicast IP, which defaults to <code>231.12.21.132</code>.
+    */
+    private static final String DEFAULT_CHANNEL_PROPERTIES_PRE = "UDP(mcast_addr=";
+
+    /**
+    * The second half of the default channel properties. They default channel properties are:
+    * <pre>
+    * UDP(mcast_addr=*.*.*.*;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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+    * UNICAST(timeout=300,600,1200,2400):\
+    * pbcast.STABLE(desired_avg_gossip=20000):\
+    * 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)
+    * </pre>
+    *
+    * Where <code>*.*.*.*</code> is the specified multicast IP, which defaults to <code>231.12.21.132</code>.
+    */
+    private static final String DEFAULT_CHANNEL_PROPERTIES_POST = ";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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):UNICAST(timeout=300,600,1200,2400):pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096;down_thread=false;up_thr [...]
+    private static final String DEFAULT_MULTICAST_IP = "231.12.21.132";
+    private NotificationBus bus;
+
+    /**
+    * Initializes the broadcasting listener by starting up a JavaGroups notification
+    * bus instance to handle incoming and outgoing messages.
+    *
+    * @param config An OSCache configuration object.
+    * @throws com.opensymphony.oscache.base.InitializationException If this listener has
+    * already been initialized.
+    */
+    public synchronized void initialize(Cache cache, Config config) throws InitializationException {
+        super.initialize(cache, config);
+
+        String properties = config.getProperty(CHANNEL_PROPERTIES);
+        String multicastIP = config.getProperty(MULTICAST_IP_PROPERTY);
+
+        if ((properties == null) && (multicastIP == null)) {
+            multicastIP = DEFAULT_MULTICAST_IP;
+        }
+
+        if (properties == null) {
+            properties = DEFAULT_CHANNEL_PROPERTIES_PRE + multicastIP.trim() + DEFAULT_CHANNEL_PROPERTIES_POST;
+        } else {
+            properties = properties.trim();
+        }
+
+        if (log.isInfoEnabled()) {
+            log.info("Starting a new JavaGroups broadcasting listener with properties=" + properties);
+        }
+
+        try {
+            bus = new NotificationBus(BUS_NAME, properties);
+            bus.start();
+            bus.getChannel().setOpt(Channel.LOCAL, new Boolean(false));
+            bus.setConsumer(this);
+            log.info("JavaGroups clustering support started successfully");
+        } catch (Exception e) {
+            throw new InitializationException("Initialization failed: " + e);
+        }
+    }
+
+    /**
+    * Shuts down the JavaGroups being managed by this listener. This
+    * occurs once the cache is shut down and this listener is no longer
+    * in use.
+    *
+    * @throws com.opensymphony.oscache.base.FinalizationException
+    */
+    public synchronized void finialize() throws FinalizationException {
+        if (log.isInfoEnabled()) {
+            log.info("JavaGroups shutting down...");
+        }
+
+        // It's possible that the notification bus is null (CACHE-154)
+        if (bus != null) {
+            bus.stop();
+            bus = null;
+        } else {
+            log.warn("Notification bus wasn't initialized or finialize was invoked before!");
+        }
+
+        if (log.isInfoEnabled()) {
+            log.info("JavaGroups shutdown complete.");
+        }
+    }
+
+    /**
+    * Uses JavaGroups to broadcast the supplied notification message across the cluster.
+    *
+    * @param message The cluster nofication message to broadcast.
+    */
+    protected void sendNotification(ClusterNotification message) {
+        bus.sendNotification(message);
+    }
+
+    /**
+    * Handles incoming notification messages from JavaGroups. This method should
+    * never be called directly.
+    *
+    * @param serializable The incoming message object. This must be a {@link ClusterNotification}.
+    */
+    public void handleNotification(Serializable serializable) {
+        if (!(serializable instanceof ClusterNotification)) {
+            log.error("An unknown cluster notification message received (class=" + serializable.getClass().getName() + "). Notification ignored.");
+
+            return;
+        }
+
+        handleClusterNotification((ClusterNotification) serializable);
+    }
+
+    /**
+    * We are not using the caching, so we just return something that identifies
+    * us. This method should never be called directly.
+    */
+    public Serializable getCache() {
+        return "JavaGroupsBroadcastingListener: " + bus.getLocalAddress();
+    }
+
+    /**
+    * A callback that is fired when a new member joins the cluster. This
+    * method should never be called directly.
+    *
+    * @param address The address of the member who just joined.
+    */
+    public void memberJoined(Address address) {
+        if (log.isInfoEnabled()) {
+            log.info("A new member at address '" + address + "' has joined the cluster");
+        }
+    }
+
+    /**
+    * A callback that is fired when an existing member leaves the cluster.
+    * This method should never be called directly.
+    *
+    * @param address The address of the member who left.
+    */
+    public void memberLeft(Address address) {
+        if (log.isInfoEnabled()) {
+            log.info("Member at address '" + address + "' left the cluster");
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/clustersupport/package.html b/src/java/com/opensymphony/oscache/plugins/clustersupport/package.html
new file mode 100644
index 0000000..75fc50c
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/clustersupport/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides support for broadcasting flush events so that OSCache can function across a
+cluster.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+  <li><a href="http://www.opensymphony.com/oscache/clustering.jsp">Clustering OSCache</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+ at since 2.0
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/plugins/diskpersistence/AbstractDiskPersistenceListener.java b/src/java/com/opensymphony/oscache/plugins/diskpersistence/AbstractDiskPersistenceListener.java
new file mode 100644
index 0000000..04db800
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/diskpersistence/AbstractDiskPersistenceListener.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.CachePersistenceException;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.*;
+
+import java.util.Set;
+
+import javax.servlet.jsp.PageContext;
+
+/**
+ * Persist the cache data to disk.
+ *
+ * The code in this class is totally not thread safe it is the resonsibility
+ * of the cache using this persistence listener to handle the concurrency.
+ *
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @author <a href="mailto:amarch at soe.sony.com">Andres March</a>
+ */
+public abstract class AbstractDiskPersistenceListener implements PersistenceListener, Serializable {
+    public final static String CACHE_PATH_KEY = "cache.path";
+
+    /**
+    * File extension for disk cache file
+    */
+    protected final static String CACHE_EXTENSION = "cache";
+
+    /**
+    * The directory that cache groups are stored under
+    */
+    protected final static String GROUP_DIRECTORY = "__groups__";
+
+    /**
+    * Sub path name for application cache
+    */
+    protected final static String APPLICATION_CACHE_SUBPATH = "application";
+
+    /**
+    * Sub path name for session cache
+    */
+    protected final static String SESSION_CACHE_SUBPATH = "session";
+
+    /**
+    * Property to get the temporary working directory of the servlet container.
+    */
+    protected static final String CONTEXT_TMPDIR = "javax.servlet.context.tempdir";
+    private static transient final Log log = LogFactory.getLog(AbstractDiskPersistenceListener.class);
+
+    /**
+    * Base path where the disk cache reside.
+    */
+    private File cachePath = null;
+    private File contextTmpDir;
+
+    /**
+    * Root path for disk cache
+    */
+    private String root = null;
+
+    /**
+    *        Get the physical cache path on disk.
+    *
+    *        @return        A file representing the physical cache location.
+    */
+    public File getCachePath() {
+        return cachePath;
+    }
+
+    /**
+    *        Get the root directory for persisting the cache on disk.
+    *        This path includes scope and sessionId, if any.
+    *
+    *        @return        A String representing the root directory.
+    */
+    public String getRoot() {
+        return root;
+    }
+
+    /**
+    *        Get the servlet context tmp directory.
+    *
+    *        @return        A file representing the servlet context tmp directory.
+    */
+    public File getContextTmpDir() {
+        return contextTmpDir;
+    }
+
+    /**
+    * Verify if a group exists in the cache
+    *
+    * @param group The group name to check
+    * @return True if it exists
+    * @throws CachePersistenceException
+    */
+    public boolean isGroupStored(String group) throws CachePersistenceException {
+        try {
+            File file = getCacheGroupFile(group);
+
+            return file.exists();
+        } catch (Exception e) {
+            throw new CachePersistenceException("Unable verify group '" + group + "' exists in the cache: " + e);
+        }
+    }
+
+    /**
+    * Verify if an object is currently stored in the cache
+    *
+    * @param key The object key
+    * @return True if it exists
+    * @throws CachePersistenceException
+    */
+    public boolean isStored(String key) throws CachePersistenceException {
+        try {
+            File file = getCacheFile(key);
+
+            return file.exists();
+        } catch (Exception e) {
+            throw new CachePersistenceException("Unable verify id '" + key + "' is stored in the cache: " + e);
+        }
+    }
+
+    /**
+    * Clears the whole cache directory, starting from the root
+    *
+    * @throws CachePersistenceException
+    */
+    public void clear() throws CachePersistenceException {
+        clear(root);
+    }
+
+    /**
+    * Initialises this <tt>DiskPersistenceListener</tt> using the supplied
+    * configuration.
+    *
+    * @param config The OSCache configuration
+    */
+    public PersistenceListener configure(Config config) {
+        String sessionId = null;
+        int scope = 0;
+        initFileCaching(config.getProperty(CACHE_PATH_KEY));
+
+        if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) {
+            sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID);
+        }
+
+        if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) {
+            scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE));
+        }
+
+        StringBuffer root = new StringBuffer(getCachePath().getPath());
+        root.append("/");
+        root.append(getPathPart(scope));
+
+        if ((sessionId != null) && (sessionId.length() > 0)) {
+            root.append("/");
+            root.append(sessionId);
+        }
+
+        this.root = root.toString();
+        this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
+
+        return this;
+    }
+
+    /**
+    * Delete a single cache entry.
+    *
+    * @param key The object key to delete
+    * @throws CachePersistenceException
+    */
+    public void remove(String key) throws CachePersistenceException {
+        File file = getCacheFile(key);
+        remove(file);
+    }
+
+    /**
+    * Deletes an entire group from the cache.
+    *
+    * @param groupName The name of the group to delete
+    * @throws CachePersistenceException
+    */
+    public void removeGroup(String groupName) throws CachePersistenceException {
+        File file = getCacheGroupFile(groupName);
+        remove(file);
+    }
+
+    /**
+    * Retrieve an object from the disk
+    *
+    * @param key The object key
+    * @return The retrieved object
+    * @throws CachePersistenceException
+    */
+    public Object retrieve(String key) throws CachePersistenceException {
+        return retrieve(getCacheFile(key));
+    }
+
+    /**
+    * Retrieves a group from the cache, or <code>null</code> if the group
+    * file could not be found.
+    *
+    * @param groupName The name of the group to retrieve.
+    * @return A <code>Set</code> containing keys of all of the cache
+    * entries that belong to this group.
+    * @throws CachePersistenceException
+    */
+    public Set retrieveGroup(String groupName) throws CachePersistenceException {
+        File groupFile = getCacheGroupFile(groupName);
+
+        try {
+            return (Set) retrieve(groupFile);
+        } catch (ClassCastException e) {
+            throw new CachePersistenceException("Group file " + groupFile + " was not persisted as a Set: " + e);
+        }
+    }
+
+    /**
+    * Stores an object in cache
+    *
+    * @param key The object's key
+    * @param obj The object to store
+    * @throws CachePersistenceException
+    */
+    public void store(String key, Object obj) throws CachePersistenceException {
+        File file = getCacheFile(key);
+        store(file, obj);
+    }
+
+    /**
+    * Stores a group in the persistent cache. This will overwrite any existing
+    * group with the same name
+    */
+    public void storeGroup(String groupName, Set group) throws CachePersistenceException {
+        File groupFile = getCacheGroupFile(groupName);
+        store(groupFile, group);
+    }
+
+    /**
+    * Allows to translate to the temp dir of the servlet container if cachePathStr
+    * is javax.servlet.context.tempdir.
+    *
+    * @param cachePathStr  Cache path read from the properties file.
+    * @return Adjusted cache path
+    */
+    protected String adjustFileCachePath(String cachePathStr) {
+        if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
+            cachePathStr = contextTmpDir.getAbsolutePath();
+        }
+
+        return cachePathStr;
+    }
+
+    /**
+    *        Set caching to file on or off.
+    *  If the <code>cache.path</code> property exists, we assume file caching is turned on.
+    *        By the same token, to turn off file caching just remove this property.
+    */
+    protected void initFileCaching(String cachePathStr) {
+        if (cachePathStr != null) {
+            cachePath = new File(cachePathStr);
+
+            try {
+                if (!cachePath.exists()) {
+                    if (log.isInfoEnabled()) {
+                        log.info("cache.path '" + cachePathStr + "' does not exist, creating");
+                    }
+
+                    cachePath.mkdirs();
+                }
+
+                if (!cachePath.isDirectory()) {
+                    log.error("cache.path '" + cachePathStr + "' is not a directory");
+                    cachePath = null;
+                } else if (!cachePath.canWrite()) {
+                    log.error("cache.path '" + cachePathStr + "' is not a writable location");
+                    cachePath = null;
+                }
+            } catch (Exception e) {
+                log.error("cache.path '" + cachePathStr + "' could not be used", e);
+                cachePath = null;
+            }
+        } else {
+            // Use default value
+        }
+    }
+    
+    // try 30s to delete the file
+    private static final long DELETE_THREAD_SLEEP = 500;
+    private static final int DELETE_COUNT = 60; 
+
+    protected void remove(File file) throws CachePersistenceException {
+        int count = DELETE_COUNT;
+        try {
+            // Loop until we are able to delete (No current read).
+            // The cache must ensure that there are never two concurrent threads
+            // doing write (store and delete) operations on the same item.
+            // Delete only should be enough but file.exists prevents infinite loop
+            while (file.exists() && !file.delete() && count != 0) {
+                count--;
+                try {
+                    Thread.sleep(DELETE_THREAD_SLEEP);
+                } catch (InterruptedException ignore) {
+                } 
+            }
+        } catch (Exception e) {
+            throw new CachePersistenceException("Unable to remove '" + file + "' from the cache: " + e);
+        }
+        if (file.exists() && count == 0) {
+            throw new CachePersistenceException("Unable to delete '" + file + "' from the cache. "+DELETE_COUNT+" attempts at "+DELETE_THREAD_SLEEP+" milliseconds intervals.");
+        }
+    }
+
+    /**
+    * Stores an object using the supplied file object
+    *
+    * @param file The file to use for storing the object
+    * @param obj the object to store
+    * @throws CachePersistenceException
+    */
+    protected void store(File file, Object obj) throws CachePersistenceException {
+        // check if file exists before testing if parent exists
+        if (!file.exists()) {
+            // check if the directory structure required exists and create it if it doesn't
+            File filepath = new File(file.getParent());
+
+            try {
+                if (!filepath.exists()) {
+                    filepath.mkdirs();
+                }
+            } catch (Exception e) {
+                throw new CachePersistenceException("Unable to create the directory " + filepath);
+            }
+        }
+
+        // Write the object to disk
+        try {
+            FileOutputStream fout = new FileOutputStream(file);
+            try {
+                ObjectOutputStream oout = new ObjectOutputStream(new BufferedOutputStream(fout));
+                try {
+                    oout.writeObject(obj);
+                    oout.flush();
+                } finally {
+                    try {
+                        oout.close();
+                    } catch (Exception e) {
+                    }
+                }
+            } finally {
+                try {
+                    fout.close();
+                } catch (Exception e) {
+                }
+            }
+        } catch (Exception e) {
+            int count = DELETE_COUNT;
+            while (file.exists() && !file.delete() && count != 0) {
+                count--;
+                try {
+                    Thread.sleep(DELETE_THREAD_SLEEP);
+                } catch (InterruptedException ignore) {
+                } 
+            }
+            throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage());
+        }
+    }
+
+    /**
+    * Build fully qualified cache file for the specified cache entry key.
+    *
+    * @param key   Cache Entry Key.
+    * @return File reference.
+    */
+    protected File getCacheFile(String key) {
+        char[] fileChars = getCacheFileName(key);
+
+        File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION);
+
+        return file;
+    }
+
+    /**
+    * Build cache file name for the specified cache entry key.
+    *
+    * @param key   Cache Entry Key.
+    * @return char[] file name.
+    */
+    protected abstract char[] getCacheFileName(String key);
+
+    /**
+    * Builds a fully qualified file name that specifies a cache group entry.
+    *
+    * @param group The name of the group
+    * @return A File reference
+    */
+    private File getCacheGroupFile(String group) {
+        int AVERAGE_PATH_LENGTH = 30;
+
+        if ((group == null) || (group.length() == 0)) {
+            throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile.");
+        }
+
+        StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
+
+        // Build a fully qualified file name for this group
+        path.append(GROUP_DIRECTORY).append('/');
+        path.append(getCacheFileName(group)).append('.').append(CACHE_EXTENSION);
+
+        return new File(root, path.toString());
+    }
+
+    /**
+    * This allows to persist different scopes in different path in the case of
+    * file caching.
+    *
+    * @param scope   Cache scope.
+    * @return The scope subpath
+    */
+    private String getPathPart(int scope) {
+        if (scope == PageContext.SESSION_SCOPE) {
+            return SESSION_CACHE_SUBPATH;
+        } else {
+            return APPLICATION_CACHE_SUBPATH;
+        }
+    }
+
+    /**
+    * Clears a whole directory, starting from the specified
+    * directory
+    *
+    * @param baseDirName The root directory to delete
+    * @throws CachePersistenceException
+    */
+    private void clear(String baseDirName) throws CachePersistenceException {
+        File baseDir = new File(baseDirName);
+        File[] fileList = baseDir.listFiles();
+
+        try {
+            if (fileList != null) {
+                // Loop through all the files and directory to delete them
+                for (int count = 0; count < fileList.length; count++) {
+                    if (fileList[count].isFile()) {
+                        fileList[count].delete();
+                    } else {
+                        // Make a recursive call to delete the directory
+                        clear(fileList[count].toString());
+                        fileList[count].delete();
+                    }
+                }
+            }
+
+            // Delete the root directory
+            baseDir.delete();
+        } catch (Exception e) {
+            throw new CachePersistenceException("Unable to clear the cache directory");
+        }
+    }
+
+    /**
+    * Retrives a serialized object from the supplied file, or returns
+    * <code>null</code> if the file does not exist.
+    *
+    * @param file The file to deserialize
+    * @return The deserialized object
+    * @throws CachePersistenceException
+    */
+    private Object retrieve(File file) throws CachePersistenceException {
+        Object readContent = null;
+        boolean fileExist;
+
+        try {
+            fileExist = file.exists();
+        } catch (Exception e) {
+            throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e);
+        }
+
+        // Read the file if it exists
+        if (fileExist) {
+            ObjectInputStream oin = null;
+
+            try {
+                BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
+                oin = new ObjectInputStream(in);
+                readContent = oin.readObject();
+            } catch (Exception e) {
+                // We expect this exception to occur.
+                // This is when the item will be invalidated (written or deleted)
+                // during read.
+                // The cache has the logic to retry reading.
+                throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e);
+            } finally {
+              // HHDE: no need to close in. Will be closed by oin
+                try {
+                    oin.close();
+                } catch (Exception ex) {
+                }
+                }
+            }
+
+        return readContent;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/diskpersistence/DiskPersistenceListener.java b/src/java/com/opensymphony/oscache/plugins/diskpersistence/DiskPersistenceListener.java
new file mode 100644
index 0000000..9a3bfb1
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/diskpersistence/DiskPersistenceListener.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+
+/**
+ * Persist the cache data to disk.
+ *
+ * The code in this class is totally not thread safe it is the resonsibility
+ * of the cache using this persistence listener to handle the concurrency.
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class DiskPersistenceListener extends AbstractDiskPersistenceListener {
+    private static final String CHARS_TO_CONVERT = "./\\ :;\"\'_?";
+
+    /**
+    * Build cache file name for the specified cache entry key.
+    *
+    * @param key   Cache Entry Key.
+    * @return char[] file name.
+    */
+    protected char[] getCacheFileName(String key) {
+        if ((key == null) || (key.length() == 0)) {
+            throw new IllegalArgumentException("Invalid key '" + key + "' specified to getCacheFile.");
+        }
+
+        char[] chars = key.toCharArray();
+
+        StringBuffer sb = new StringBuffer(chars.length + 8);
+
+        for (int i = 0; i < chars.length; i++) {
+            char c = chars[i];
+            int pos = CHARS_TO_CONVERT.indexOf(c);
+ 
+            if (pos >= 0) {
+                sb.append('_');
+                sb.append(i);
+            } else {
+                sb.append(c);
+            }
+        }
+
+        char[] fileChars = new char[sb.length()];
+        sb.getChars(0, fileChars.length, fileChars, 0);
+        return fileChars;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/diskpersistence/HashDiskPersistenceListener.java b/src/java/com/opensymphony/oscache/plugins/diskpersistence/HashDiskPersistenceListener.java
new file mode 100644
index 0000000..22b9f29
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/diskpersistence/HashDiskPersistenceListener.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+
+import java.io.File;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Persists cache data to disk. Provides a hash of the standard key name as the file name.
+ *
+ * A configurable hash algorithm is used to create a digest of the cache key for the
+ * disk filename. This is to allow for more sane filenames for objects which dont generate
+ * friendly cache keys.
+ *
+ * @author <a href="mailto:jparrott at soe.sony.com">Jason Parrott</a>
+ */
+public class HashDiskPersistenceListener extends AbstractDiskPersistenceListener {
+    
+    private static final int DIR_LEVELS = 3;
+    
+    public final static String HASH_ALGORITHM_KEY = "cache.persistence.disk.hash.algorithm";
+    public final static String DEFAULT_HASH_ALGORITHM = "MD5";
+    protected MessageDigest md = null;
+
+    /**
+     * Initializes the <tt>HashDiskPersistenceListener</tt>. Namely this involves only setting up the
+     * message digester to hash the key values.
+     * @see com.opensymphony.oscache.base.persistence.PersistenceListener#configure(com.opensymphony.oscache.base.Config)
+     */
+    public PersistenceListener configure(Config config) {
+        try {
+            if (config.getProperty(HashDiskPersistenceListener.HASH_ALGORITHM_KEY) != null) {
+                try {
+                    md = MessageDigest.getInstance(config.getProperty(HashDiskPersistenceListener.HASH_ALGORITHM_KEY));
+                } catch (NoSuchAlgorithmException e) {
+                    md = MessageDigest.getInstance(HashDiskPersistenceListener.DEFAULT_HASH_ALGORITHM);
+                }
+            } else {
+                md = MessageDigest.getInstance(HashDiskPersistenceListener.DEFAULT_HASH_ALGORITHM);
+            }
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            throw new RuntimeException("No hash algorithm available for disk persistence", e);
+        }
+
+        return super.configure(config);
+    }
+
+    /**
+     * Generates a file name for the given cache key. In this case the file name is attempted to be
+     * generated from the hash of the standard key name. Cache algorithm is configured via the
+     * <em>cache.persistence.disk.hash.algorithm</em> configuration variable.
+     * @param key cache entry key
+     * @return char[] file name
+     */
+    protected synchronized char[] getCacheFileName(String key) {
+        if ((key == null) || (key.length() == 0)) {
+            throw new IllegalArgumentException("Invalid key '" + key + "' specified to getCacheFile.");
+        }
+
+        String hexDigest = byteArrayToHexString(md.digest(key.getBytes()));
+
+        // CACHE-249: Performance improvement for large disk persistence usage
+        StringBuffer filename = new StringBuffer(hexDigest.length() + 2 * DIR_LEVELS);
+        for (int i=0; i < DIR_LEVELS; i++) {
+            filename.append(hexDigest.charAt(i)).append(File.separator);
+        }
+        filename.append(hexDigest);
+
+        return filename.toString().toCharArray();
+    }
+
+    /**
+     * Nibble conversion. Thanks to our friends at:
+     * http://www.devx.com/tips/Tip/13540
+     * @param in the byte array to convert
+     * @return a java.lang.String based version of they byte array
+     */
+    static String byteArrayToHexString(byte[] in) {
+        if ((in == null) || (in.length <= 0)) {
+            return null;
+        }
+
+        StringBuffer out = new StringBuffer(in.length * 2);
+        
+        for (int i = 0; i < in.length; i++) {
+            byte ch = (byte) (in[i] & 0xF0); // Strip off high nibble
+            ch = (byte) (ch >>> 4);
+
+            // shift the bits down
+            ch = (byte) (ch & 0x0F);
+
+            //	 must do this is high order bit is on!
+            out.append(PSEUDO[(int) ch]); // convert the nibble to a String Character
+            ch = (byte) (in[i] & 0x0F); // Strip off low nibble 
+            out.append(PSEUDO[(int) ch]); // convert the nibble to a String Character
+        }
+
+        return out.toString();
+    }
+    
+    static final String[] PSEUDO = {
+            "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D",
+            "E", "F"
+        };
+
+}
diff --git a/src/java/com/opensymphony/oscache/plugins/diskpersistence/package.html b/src/java/com/opensymphony/oscache/plugins/diskpersistence/package.html
new file mode 100644
index 0000000..b7081d1
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/plugins/diskpersistence/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides support for persisting cached objects to disk.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/util/ClassLoaderUtil.java b/src/java/com/opensymphony/oscache/util/ClassLoaderUtil.java
new file mode 100644
index 0000000..2c2153c
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/util/ClassLoaderUtil.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.util;
+
+
+/**
+ * <p>This code is borrowed directly from OSCore, but is duplicated
+ * here to avoid having to add a dependency on the entire OSCore jar.</p>
+ *
+ * <p>If much more code from OSCore is needed then it might be wiser to
+ * bite the bullet and add a dependency.</p>
+ */
+public class ClassLoaderUtil {
+    
+    private ClassLoaderUtil() {
+    }
+    
+    /**
+     * Load a class with a given name.
+     *
+     * It will try to load the class in the following order:
+     * <ul>
+     *  <li>From Thread.currentThread().getContextClassLoader()
+     *  <li>Using the basic Class.forName()
+     *  <li>From ClassLoaderUtil.class.getClassLoader()
+     *  <li>From the callingClass.getClassLoader()
+     * </ul>
+     *
+     * @param className The name of the class to load
+     * @param callingClass The Class object of the calling object
+     * @throws ClassNotFoundException If the class cannot be found anywhere.
+     */
+    public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException {
+        try {
+            return Thread.currentThread().getContextClassLoader().loadClass(className);
+        } catch (ClassNotFoundException e) {
+            try {
+                return Class.forName(className);
+            } catch (ClassNotFoundException ex) {
+                try {
+                    return ClassLoaderUtil.class.getClassLoader().loadClass(className);
+                } catch (ClassNotFoundException exc) {
+                    return callingClass.getClassLoader().loadClass(className);
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/util/FastCronParser.java b/src/java/com/opensymphony/oscache/util/FastCronParser.java
new file mode 100644
index 0000000..b1d6dcf
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/util/FastCronParser.java
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.util;
+
+import java.text.ParseException;
+
+import java.util.*;
+import java.util.Calendar;
+
+/**
+ * Parses cron expressions and determines at what time in the past is the
+ * most recent match for the supplied expression.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @author $Author: ltorunski $
+ * @version $Revision: 340 $
+ */
+public class FastCronParser {
+    private static final int NUMBER_OF_CRON_FIELDS = 5;
+    private static final int MINUTE = 0;
+    private static final int HOUR = 1;
+    private static final int DAY_OF_MONTH = 2;
+    private static final int MONTH = 3;
+    private static final int DAY_OF_WEEK = 4;
+
+    // Lookup tables that hold the min/max/size of each of the above field types.
+    // These tables are precalculated for performance.
+    private static final int[] MIN_VALUE = {0, 0, 1, 1, 0};
+    private static final int[] MAX_VALUE = {59, 23, 31, 12, 6};
+
+    /**
+     * A lookup table holding the number of days in each month (with the obvious exception
+     * that February requires special handling).
+     */
+    private static final int[] DAYS_IN_MONTH = {
+        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+
+    /**
+     * Holds the raw cron expression that this parser is handling.
+     */
+    private String cronExpression = null;
+
+    /**
+    * This is the main lookup table that holds a parsed cron expression. each long
+    * represents one of the above field types. Bits in each long value correspond
+    * to one of the possbile field values - eg, for the minute field, bits 0 -> 59 in
+    * <code>lookup[MINUTE]</code> map to minutes 0 -> 59 respectively. Bits are set if
+    * the corresponding value is enabled. So if the minute field in the cron expression
+    * was <code>"0,2-8,50"</code>, bits 0, 2, 3, 4, 5, 6, 7, 8 and 50 will be set.
+    * If the cron expression is <code>"*"</code>, the long value is set to
+    * <code>Long.MAX_VALUE</code>.
+    */
+    private long[] lookup = {
+        Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE,
+        Long.MAX_VALUE
+    };
+
+    /**
+    * This is based on the contents of the <code>lookup</code> table. It holds the
+    * <em>highest</em> valid field value for each field type.
+    */
+    private int[] lookupMax = {-1, -1, -1, -1, -1};
+
+    /**
+    * This is based on the contents of the <code>lookup</code> table. It holds the
+    * <em>lowest</em> valid field value for each field type.
+    */
+    private int[] lookupMin = {
+        Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+        Integer.MAX_VALUE, Integer.MAX_VALUE
+    };
+
+    /**
+    * Creates a FastCronParser that uses a default cron expression of <code>"* * * * *"</cron>.
+    * This will match any time that is supplied.
+    */
+    public FastCronParser() {
+    }
+
+    /**
+    * Constructs a new FastCronParser based on the supplied expression.
+    *
+    * @throws ParseException if the supplied expression is not a valid cron expression.
+    */
+    public FastCronParser(String cronExpression) throws ParseException {
+        setCronExpression(cronExpression);
+    }
+
+    /**
+    * Resets the cron expression to the value supplied.
+    *
+    * @param cronExpression the new cron expression.
+    *
+    * @throws ParseException if the supplied expression is not a valid cron expression.
+    */
+    public void setCronExpression(String cronExpression) throws ParseException {
+        if (cronExpression == null) {
+            throw new IllegalArgumentException("Cron time expression cannot be null");
+        }
+
+        this.cronExpression = cronExpression;
+        parseExpression(cronExpression);
+    }
+
+    /**
+    * Retrieves the current cron expression.
+    *
+    * @return the current cron expression.
+    */
+    public String getCronExpression() {
+        return this.cronExpression;
+    }
+
+    /**
+    * Determines whether this cron expression matches a date/time that is more recent
+    * than the one supplied.
+    *
+    * @param time The time to compare the cron expression against.
+    *
+    * @return <code>true</code> if the cron expression matches a time that is closer
+    * to the current time than the supplied time is, <code>false</code> otherwise.
+    */
+    public boolean hasMoreRecentMatch(long time) {
+        return time < getTimeBefore(System.currentTimeMillis());
+    }
+
+    /**
+    * Find the most recent time that matches this cron expression. This time will
+    * always be in the past, ie a lower value than the supplied time.
+    *
+    * @param time The time (in milliseconds) that we're using as our upper bound.
+    *
+    * @return The time (in milliseconds) when this cron event last occurred.
+    */
+    public long getTimeBefore(long time) {
+        // It would be nice to get rid of the Calendar class for speed, but it's a lot of work...
+        // We create this
+        Calendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(time);
+
+        int minute = cal.get(Calendar.MINUTE);
+        int hour = cal.get(Calendar.HOUR_OF_DAY);
+        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
+        int month = cal.get(Calendar.MONTH) + 1; // Calendar is 0-based for this field, and we are 1-based
+        int year = cal.get(Calendar.YEAR);
+
+        long validMinutes = lookup[MINUTE];
+        long validHours = lookup[HOUR];
+        long validDaysOfMonth = lookup[DAY_OF_MONTH];
+        long validMonths = lookup[MONTH];
+        long validDaysOfWeek = lookup[DAY_OF_WEEK];
+
+        // Find out if we have a Day of Week or Day of Month field
+        boolean haveDOM = validDaysOfMonth != Long.MAX_VALUE;
+        boolean haveDOW = validDaysOfWeek != Long.MAX_VALUE;
+
+        boolean skippedNonLeapYear = false;
+
+        while (true) {
+            boolean retry = false;
+
+            // Clean up the month if it was wrapped in a previous iteration
+            if (month < 1) {
+                month += 12;
+                year--;
+            }
+
+            // get month...................................................
+            boolean found = false;
+
+            if (validMonths != Long.MAX_VALUE) {
+                for (int i = month + 11; i > (month - 1); i--) {
+                    int testMonth = (i % 12) + 1;
+
+                    // Check if the month is valid
+                    if (((1L << (testMonth - 1)) & validMonths) != 0) {
+                        if ((testMonth > month) || skippedNonLeapYear) {
+                            year--;
+                        }
+
+                        // Check there are enough days in this month (catches non leap-years trying to match the 29th Feb)
+                        int numDays = numberOfDaysInMonth(testMonth, year);
+
+                        if (!haveDOM || (numDays >= lookupMin[DAY_OF_MONTH])) {
+                            if ((month != testMonth) || skippedNonLeapYear) {
+                                // New DOM = min(maxDOM, prevDays);  ie, the highest valid value
+                                dayOfMonth = (numDays <= lookupMax[DAY_OF_MONTH]) ? numDays : lookupMax[DAY_OF_MONTH];
+                                hour = lookupMax[HOUR];
+                                minute = lookupMax[MINUTE];
+                                month = testMonth;
+                            }
+
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+
+                skippedNonLeapYear = false;
+
+                if (!found) {
+                    // The only time we drop out here is when we're searching for the 29th of February and no other date!
+                    skippedNonLeapYear = true;
+                    continue;
+                }
+            }
+
+            // Clean up if the dayOfMonth was wrapped. This takes leap years into account.
+            if (dayOfMonth < 1) {
+                month--;
+                dayOfMonth += numberOfDaysInMonth(month, year);
+                hour = lookupMax[HOUR];
+                continue;
+            }
+
+            // get day...................................................
+            if (haveDOM && !haveDOW) { // get day using just the DAY_OF_MONTH token
+
+                int daysInThisMonth = numberOfDaysInMonth(month, year);
+                int daysInPreviousMonth = numberOfDaysInMonth(month - 1, year);
+
+                // Find the highest valid day that is below the current day
+                for (int i = dayOfMonth + 30; i > (dayOfMonth - 1); i--) {
+                    int testDayOfMonth = (i % 31) + 1;
+
+                    // Skip over any days that don't actually exist (eg 31st April)
+                    if ((testDayOfMonth <= dayOfMonth) && (testDayOfMonth > daysInThisMonth)) {
+                        continue;
+                    }
+
+                    if ((testDayOfMonth > dayOfMonth) && (testDayOfMonth > daysInPreviousMonth)) {
+                        continue;
+                    }
+
+                    if (((1L << (testDayOfMonth - 1)) & validDaysOfMonth) != 0) {
+                        if (testDayOfMonth > dayOfMonth) {
+                            // We've found a valid day, but we had to move back a month
+                            month--;
+                            retry = true;
+                        }
+
+                        if (dayOfMonth != testDayOfMonth) {
+                            hour = lookupMax[HOUR];
+                            minute = lookupMax[MINUTE];
+                        }
+
+                        dayOfMonth = testDayOfMonth;
+                        break;
+                    }
+                }
+
+                if (retry) {
+                    continue;
+                }
+            } else if (haveDOW && !haveDOM) { // get day using just the DAY_OF_WEEK token
+
+                int daysLost = 0;
+                int currentDOW = dayOfWeek(dayOfMonth, month, year);
+
+                for (int i = currentDOW + 7; i > currentDOW; i--) {
+                    int testDOW = i % 7;
+
+                    if (((1L << testDOW) & validDaysOfWeek) != 0) {
+                        dayOfMonth -= daysLost;
+
+                        if (dayOfMonth < 1) {
+                            // We've wrapped back a month
+                            month--;
+                            dayOfMonth += numberOfDaysInMonth(month, year);
+                            retry = true;
+                        }
+
+                        if (currentDOW != testDOW) {
+                            hour = lookupMax[HOUR];
+                            minute = lookupMax[MINUTE];
+                        }
+
+                        break;
+                    }
+
+                    daysLost++;
+                }
+
+                if (retry) {
+                    continue;
+                }
+            }
+
+            // Clean up if the hour has been wrapped
+            if (hour < 0) {
+                hour += 24;
+                dayOfMonth--;
+                continue;
+            }
+
+            // get hour...................................................
+            if (validHours != Long.MAX_VALUE) {
+                // Find the highest valid hour that is below the current hour
+                for (int i = hour + 24; i > hour; i--) {
+                    int testHour = i % 24;
+
+                    if (((1L << testHour) & validHours) != 0) {
+                        if (testHour > hour) {
+                            // We've found an hour, but we had to move back a day
+                            dayOfMonth--;
+                            retry = true;
+                        }
+
+                        if (hour != testHour) {
+                            minute = lookupMax[MINUTE];
+                        }
+
+                        hour = testHour;
+                        break;
+                    }
+                }
+
+                if (retry) {
+                    continue;
+                }
+            }
+
+            // get minute.................................................
+            if (validMinutes != Long.MAX_VALUE) {
+                // Find the highest valid minute that is below the current minute
+                for (int i = minute + 60; i > minute; i--) {
+                    int testMinute = i % 60;
+
+                    if (((1L << testMinute) & validMinutes) != 0) {
+                        if (testMinute > minute) {
+                            // We've found a minute, but we had to move back an hour
+                            hour--;
+                            retry = true;
+                        }
+
+                        minute = testMinute;
+                        break;
+                    }
+                }
+
+                if (retry) {
+                    continue;
+                }
+            }
+
+            break;
+        }
+
+        // OK, all done. Return the adjusted time value (adjusting this is faster than creating a new Calendar object)
+        cal.set(Calendar.YEAR, year);
+        cal.set(Calendar.MONTH, month - 1); // Calendar is 0-based for this field, and we are 1-based
+        cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+        cal.set(Calendar.HOUR_OF_DAY, hour);
+        cal.set(Calendar.MINUTE, minute);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+
+        return cal.getTime().getTime();
+    }
+
+    /**
+    * Takes a cron expression as an input parameter, and extracts from it the
+    * relevant minutes/hours/days/months that the expression matches.
+    *
+    * @param expression  A valid cron expression.
+    * @throws ParseException If the supplied expression could not be parsed.
+    */
+    private void parseExpression(String expression) throws ParseException {
+        try {
+            // Reset all the lookup data
+            for (int i = 0; i < lookup.length; lookup[i++] = 0) {
+                lookupMin[i] = Integer.MAX_VALUE;
+                lookupMax[i] = -1;
+            }
+
+            // Create some character arrays to hold the extracted field values
+            char[][] token = new char[NUMBER_OF_CRON_FIELDS][];
+
+            // Extract the supplied expression into another character array
+            // for speed
+            int length = expression.length();
+            char[] expr = new char[length];
+            expression.getChars(0, length, expr, 0);
+
+            int field = 0;
+            int startIndex = 0;
+            boolean inWhitespace = true;
+
+            // Extract the various cron fields from the expression
+            for (int i = 0; (i < length) && (field < NUMBER_OF_CRON_FIELDS);
+                    i++) {
+                boolean haveChar = (expr[i] != ' ') && (expr[i] != '\t');
+
+                if (haveChar) {
+                    // We have a text character of some sort
+                    if (inWhitespace) {
+                        startIndex = i; // Remember the start of this token
+                        inWhitespace = false;
+                    }
+                }
+
+                if (i == (length - 1)) { // Adjustment for when we reach the end of the expression
+                    i++;
+                }
+
+                if (!(haveChar || inWhitespace) || (i == length)) {
+                    // We've reached the end of a token. Copy it into a new char array
+                    token[field] = new char[i - startIndex];
+                    System.arraycopy(expr, startIndex, token[field], 0, i - startIndex);
+                    inWhitespace = true;
+                    field++;
+                }
+            }
+
+            if (field < NUMBER_OF_CRON_FIELDS) {
+                throw new ParseException("Unexpected end of expression while parsing \"" + expression + "\". Cron expressions require 5 separate fields.", length);
+            }
+
+            // OK, we've broken the string up into the 5 cron fields, now lets add
+            // each field to their lookup table.
+            for (field = 0; field < NUMBER_OF_CRON_FIELDS; field++) {
+                startIndex = 0;
+
+                boolean inDelimiter = true;
+
+                // We add each comma-delimited element seperately.
+                int elementLength = token[field].length;
+
+                for (int i = 0; i < elementLength; i++) {
+                    boolean haveElement = token[field][i] != ',';
+
+                    if (haveElement) {
+                        // We have a character from an element in the token
+                        if (inDelimiter) {
+                            startIndex = i;
+                            inDelimiter = false;
+                        }
+                    }
+
+                    if (i == (elementLength - 1)) { // Adjustment for when we reach the end of the token
+                        i++;
+                    }
+
+                    if (!(haveElement || inDelimiter) || (i == elementLength)) {
+                        // We've reached the end of an element. Copy it into a new char array
+                        char[] element = new char[i - startIndex];
+                        System.arraycopy(token[field], startIndex, element, 0, i - startIndex);
+
+                        // Add the element to our datastructure.
+                        storeExpressionValues(element, field);
+
+                        inDelimiter = true;
+                    }
+                }
+
+                if (lookup[field] == 0) {
+                    throw new ParseException("Token " + new String(token[field]) + " contains no valid entries for this field.", 0);
+                }
+            }
+
+            // Remove any months that will never be valid
+            switch (lookupMin[DAY_OF_MONTH]) {
+                case 31:
+                    lookup[MONTH] &= (0xFFF - 0x528); // Binary 010100101000 - the months that have 30 days
+                case 30:
+                    lookup[MONTH] &= (0xFFF - 0x2); // Binary 000000000010 - February
+
+                    if (lookup[MONTH] == 0) {
+                        throw new ParseException("The cron expression \"" + expression + "\" will never match any months - the day of month field is out of range.", 0);
+                    }
+            }
+
+            // Check that we don't have both a day of month and a day of week field.
+            if ((lookup[DAY_OF_MONTH] != Long.MAX_VALUE) && (lookup[DAY_OF_WEEK] != Long.MAX_VALUE)) {
+                throw new ParseException("The cron expression \"" + expression + "\" is invalid. Having both a day-of-month and day-of-week field is not supported.", 0);
+            }
+        } catch (Exception e) {
+            if (e instanceof ParseException) {
+                throw (ParseException) e;
+            } else {
+                throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0);
+            }
+        }
+    }
+
+    /**
+    * Stores the values for the supplied cron element into the specified field.
+    *
+    * @param element  The cron element to store. A cron element is a single component
+    * of a cron expression. For example, the complete set of elements for the cron expression
+    * <code>30 0,6,12,18 * * *</code> would be <code>{"30", "0", "6", "12", "18", "*", "*", "*"}</code>.
+    * @param field  The field that this expression belongs to. Valid values are {@link #MINUTE},
+    * {@link #HOUR}, {@link #DAY_OF_MONTH}, {@link #MONTH} and {@link #DAY_OF_WEEK}.
+    *
+    * @throws ParseException if there was a problem parsing the supplied element.
+    */
+    private void storeExpressionValues(char[] element, int field) throws ParseException {
+        int i = 0;
+
+        int start = -99;
+        int end = -99;
+        int interval = -1;
+        boolean wantValue = true;
+        boolean haveInterval = false;
+
+        while ((interval < 0) && (i < element.length)) {
+            char ch = element[i++];
+
+            // Handle the wildcard character - it can only ever occur at the start of an element
+            if ((i == 1) && (ch == '*')) {
+                // Handle the special case where we have '*' and nothing else
+                if (i >= element.length) {
+                    addToLookup(-1, -1, field, 1);
+                    return;
+                }
+
+                start = -1;
+                end = -1;
+                wantValue = false;
+                continue;
+            }
+
+            if (wantValue) {
+                // Handle any numbers
+                if ((ch >= '0') && (ch <= '9')) {
+                    ValueSet vs = getValue(ch - '0', element, i);
+
+                    if (start == -99) {
+                        start = vs.value;
+                    } else if (!haveInterval) {
+                        end = vs.value;
+                    } else {
+                        if (end == -99) {
+                            end = MAX_VALUE[field];
+                        }
+
+                        interval = vs.value;
+                    }
+
+                    i = vs.pos;
+                    wantValue = false;
+                    continue;
+                }
+
+                if (!haveInterval && (end == -99)) {
+                    // Handle any months that have been suplied as words
+                    if (field == MONTH) {
+                        if (start == -99) {
+                            start = getMonthVal(ch, element, i++);
+                        } else {
+                            end = getMonthVal(ch, element, i++);
+                        }
+
+                        wantValue = false;
+
+                        // Skip past the rest of the month name
+                        while (++i < element.length) {
+                            int c = element[i] | 0x20;
+
+                            if ((c < 'a') || (c > 'z')) {
+                                break;
+                            }
+                        }
+
+                        continue;
+                    } else if (field == DAY_OF_WEEK) {
+                        if (start == -99) {
+                            start = getDayOfWeekVal(ch, element, i++);
+                        } else {
+                            end = getDayOfWeekVal(ch, element, i++);
+                        }
+
+                        wantValue = false;
+
+                        // Skip past the rest of the day name
+                        while (++i < element.length) {
+                            int c = element[i] | 0x20;
+
+                            if ((c < 'a') || (c > 'z')) {
+                                break;
+                            }
+                        }
+
+                        continue;
+                    }
+                }
+            } else {
+                // Handle the range character. A range character is only valid if we have a start but no end value
+                if ((ch == '-') && (start != -99) && (end == -99)) {
+                    wantValue = true;
+                    continue;
+                }
+
+                // Handle an interval. An interval is valid as long as we have a start value
+                if ((ch == '/') && (start != -99)) {
+                    wantValue = true;
+                    haveInterval = true;
+                    continue;
+                }
+            }
+
+            throw makeParseException("Invalid character encountered while parsing element", element, i);
+        }
+
+        if (element.length > i) {
+            throw makeParseException("Extraneous characters found while parsing element", element, i);
+        }
+
+        if (end == -99) {
+            end = start;
+        }
+
+        if (interval < 0) {
+            interval = 1;
+        }
+
+        addToLookup(start, end, field, interval);
+    }
+
+    /**
+    * Extracts a numerical value from inside a character array.
+    *
+    * @param value    The value of the first character
+    * @param element  The character array we're extracting the value from
+    * @param i        The index into the array of the next character to process
+    *
+    * @return the new index and the extracted value
+    */
+    private ValueSet getValue(int value, char[] element, int i) {
+        ValueSet result = new ValueSet();
+        result.value = value;
+
+        if (i >= element.length) {
+            result.pos = i;
+            return result;
+        }
+
+        char ch = element[i];
+
+        while ((ch >= '0') && (ch <= '9')) {
+            result.value = (result.value * 10) + (ch - '0');
+
+            if (++i >= element.length) {
+                break;
+            }
+
+            ch = element[i];
+        }
+
+        result.pos = i;
+
+        return result;
+    }
+
+    /**
+    * Adds a group of valid values to the lookup table for the specified field. This method
+    * handles ranges that increase in arbitrary step sizes. It is also possible to add a single
+    * value by specifying a range with the same start and end values.
+    *
+    * @param start The starting value for the range. Supplying a value that is less than zero
+    * will cause the minimum allowable value for the specified field to be used as the start value.
+    * @param end   The maximum value that can be added (ie the upper bound). If the step size is
+    * greater than one, this maximum value may not necessarily end up being added. Supplying a
+    * value that is less than zero will cause the maximum allowable value for the specified field
+    * to be used as the upper bound.
+    * @param field The field that the values should be added to.
+    * @param interval Specifies the step size for the range. Any values less than one will be
+    * treated as a single step interval.
+    */
+    private void addToLookup(int start, int end, int field, int interval) throws ParseException {
+        // deal with the supplied range
+        if (start == end) {
+            if (start < 0) {
+                // We're setting the entire range of values
+                start = lookupMin[field] = MIN_VALUE[field];
+                end = lookupMax[field] = MAX_VALUE[field];
+
+                if (interval <= 1) {
+                    lookup[field] = Long.MAX_VALUE;
+                    return;
+                }
+            } else {
+                // We're only setting a single value - check that it is in range
+                if (start < MIN_VALUE[field]) {
+                    throw new ParseException("Value " + start + " in field " + field + " is lower than the minimum allowable value for this field (min=" + MIN_VALUE[field] + ")", 0);
+                } else if (start > MAX_VALUE[field]) {
+                    throw new ParseException("Value " + start + " in field " + field + " is higher than the maximum allowable value for this field (max=" + MAX_VALUE[field] + ")", 0);
+                }
+            }
+        } else {
+            // For ranges, if the start is bigger than the end value then swap them over
+            if (start > end) {
+                end ^= start;
+                start ^= end;
+                end ^= start;
+            }
+
+            if (start < 0) {
+                start = MIN_VALUE[field];
+            } else if (start < MIN_VALUE[field]) {
+                throw new ParseException("Value " + start + " in field " + field + " is lower than the minimum allowable value for this field (min=" + MIN_VALUE[field] + ")", 0);
+            }
+
+            if (end < 0) {
+                end = MAX_VALUE[field];
+            } else if (end > MAX_VALUE[field]) {
+                throw new ParseException("Value " + end + " in field " + field + " is higher than the maximum allowable value for this field (max=" + MAX_VALUE[field] + ")", 0);
+            }
+        }
+
+        if (interval < 1) {
+            interval = 1;
+        }
+
+        int i = start - MIN_VALUE[field];
+
+        // Populate the lookup table by setting all the bits corresponding to the valid field values
+        for (i = start - MIN_VALUE[field]; i <= (end - MIN_VALUE[field]);
+                i += interval) {
+            lookup[field] |= (1L << i);
+        }
+
+        // Make sure we remember the minimum value set so far
+        // Keep track of the highest and lowest values that have been added to this field so far
+        if (lookupMin[field] > start) {
+            lookupMin[field] = start;
+        }
+
+        i += (MIN_VALUE[field] - interval);
+
+        if (lookupMax[field] < i) {
+            lookupMax[field] = i;
+        }
+    }
+
+    /**
+    * Indicates if a year is a leap year or not.
+    *
+    * @param year The year to check
+    *
+    * @return <code>true</code> if the year is a leap year, <code>false</code> otherwise.
+    */
+    private boolean isLeapYear(int year) {
+        return (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0);
+    }
+
+    /**
+    * Calculate the day of the week. Sunday = 0, Monday = 1, ... , Saturday = 6. The formula
+    * used is an optimized version of Zeller's Congruence.
+    *
+    * @param day        The day of the month (1-31)
+    * @param month      The month (1 - 12)
+    * @param year       The year
+    * @return
+    */
+    private int dayOfWeek(int day, int month, int year) {
+        day += ((month < 3) ? year-- : (year - 2));
+        return ((((23 * month) / 9) + day + 4 + (year / 4)) - (year / 100) + (year / 400)) % 7;
+    }
+
+    /**
+    * Retrieves the number of days in the supplied month, taking into account leap years.
+    * If the month value is outside the range <code>MIN_VALUE[MONTH] - MAX_VALUE[MONTH]</code>
+    * then the year will be adjusted accordingly and the correct number of days will still
+    * be returned.
+    *
+    * @param month The month of interest.
+    * @param year  The year we are checking.
+    *
+    * @return The number of days in the month.
+    */
+    private int numberOfDaysInMonth(int month, int year) {
+        while (month < 1) {
+            month += 12;
+            year--;
+        }
+
+        while (month > 12) {
+            month -= 12;
+            year++;
+        }
+
+        if (month == 2) {
+            return isLeapYear(year) ? 29 : 28;
+        } else {
+            return DAYS_IN_MONTH[month - 1];
+        }
+    }
+
+    /**
+    * Quickly retrieves the day of week value (Sun = 0, ... Sat = 6) that corresponds to the
+    * day name that is specified in the character array. Only the first 3 characters are taken
+    * into account; the rest are ignored.
+    *
+    * @param element The character array
+    * @param i       The index to start looking at
+    * @return        The day of week value
+    */
+    private int getDayOfWeekVal(char ch1, char[] element, int i) throws ParseException {
+        if ((i + 1) >= element.length) {
+            throw makeParseException("Unexpected end of element encountered while parsing a day name", element, i);
+        }
+
+        int ch2 = element[i] | 0x20;
+        int ch3 = element[i + 1] | 0x20;
+
+        switch (ch1 | 0x20) {
+            case 's': // Sunday, Saturday
+
+                if ((ch2 == 'u') && (ch3 == 'n')) {
+                    return 0;
+                }
+
+                if ((ch2 == 'a') && (ch3 == 't')) {
+                    return 6;
+                }
+
+                break;
+            case 'm': // Monday
+
+                if ((ch2 == 'o') && (ch3 == 'n')) {
+                    return 1;
+                }
+
+                break;
+            case 't': // Tuesday, Thursday
+
+                if ((ch2 == 'u') && (ch3 == 'e')) {
+                    return 2;
+                }
+
+                if ((ch2 == 'h') && (ch3 == 'u')) {
+                    return 4;
+                }
+
+                break;
+            case 'w': // Wednesday
+
+                if ((ch2 == 'e') && (ch3 == 'd')) {
+                    return 3;
+                }
+
+                break;
+            case 'f': // Friday
+
+                if ((ch2 == 'r') && (ch3 == 'i')) {
+                    return 5;
+                }
+
+                break;
+        }
+
+        throw makeParseException("Unexpected character while parsing a day name", element, i - 1);
+    }
+
+    /**
+    * Quickly retrieves the month value (Jan = 1, ..., Dec = 12) that corresponds to the month
+    * name that is specified in the character array. Only the first 3 characters are taken
+    * into account; the rest are ignored.
+    *
+    * @param element The character array
+    * @param i       The index to start looking at
+    * @return        The month value
+    */
+    private int getMonthVal(char ch1, char[] element, int i) throws ParseException {
+        if ((i + 1) >= element.length) {
+            throw makeParseException("Unexpected end of element encountered while parsing a month name", element, i);
+        }
+
+        int ch2 = element[i] | 0x20;
+        int ch3 = element[i + 1] | 0x20;
+
+        switch (ch1 | 0x20) {
+            case 'j': // January, June, July
+
+                if ((ch2 == 'a') && (ch3 == 'n')) {
+                    return 1;
+                }
+
+                if (ch2 == 'u') {
+                    if (ch3 == 'n') {
+                        return 6;
+                    }
+
+                    if (ch3 == 'l') {
+                        return 7;
+                    }
+                }
+
+                break;
+            case 'f': // February
+
+                if ((ch2 == 'e') && (ch3 == 'b')) {
+                    return 2;
+                }
+
+                break;
+            case 'm': // March, May
+
+                if (ch2 == 'a') {
+                    if (ch3 == 'r') {
+                        return 3;
+                    }
+
+                    if (ch3 == 'y') {
+                        return 5;
+                    }
+                }
+
+                break;
+            case 'a': // April, August
+
+                if ((ch2 == 'p') && (ch3 == 'r')) {
+                    return 4;
+                }
+
+                if ((ch2 == 'u') && (ch3 == 'g')) {
+                    return 8;
+                }
+
+                break;
+            case 's': // September
+
+                if ((ch2 == 'e') && (ch3 == 'p')) {
+                    return 9;
+                }
+
+                break;
+            case 'o': // October
+
+                if ((ch2 == 'c') && (ch3 == 't')) {
+                    return 10;
+                }
+
+                break;
+            case 'n': // November
+
+                if ((ch2 == 'o') && (ch3 == 'v')) {
+                    return 11;
+                }
+
+                break;
+            case 'd': // December
+
+                if ((ch2 == 'e') && (ch3 == 'c')) {
+                    return 12;
+                }
+
+                break;
+        }
+
+        throw makeParseException("Unexpected character while parsing a month name", element, i - 1);
+    }
+
+    /**
+    * Recreates the original human-readable cron expression based on the internal
+    * datastructure values.
+    *
+    * @return A cron expression that corresponds to the current state of the
+    * internal data structure.
+    */
+    public String getExpressionSummary() {
+        StringBuffer buf = new StringBuffer();
+
+        buf.append(getExpressionSetSummary(MINUTE)).append(' ');
+        buf.append(getExpressionSetSummary(HOUR)).append(' ');
+        buf.append(getExpressionSetSummary(DAY_OF_MONTH)).append(' ');
+        buf.append(getExpressionSetSummary(MONTH)).append(' ');
+        buf.append(getExpressionSetSummary(DAY_OF_WEEK));
+
+        return buf.toString();
+    }
+
+    /**
+    * <p>Converts the internal datastructure that holds a particular cron field into
+    * a human-readable list of values of the field's contents. For example, if the
+    * <code>DAY_OF_WEEK</code> field was submitted that had Sunday and Monday specified,
+    * the string <code>0,1</code> would be returned.</p>
+    *
+    * <p>If the field contains all possible values, <code>*</code> will be returned.
+    *
+    * @param field The field.
+    *
+    * @return A human-readable string representation of the field's contents.
+    */
+    private String getExpressionSetSummary(int field) {
+        if (lookup[field] == Long.MAX_VALUE) {
+            return "*";
+        }
+
+        StringBuffer buf = new StringBuffer();
+
+        boolean first = true;
+
+        for (int i = MIN_VALUE[field]; i <= MAX_VALUE[field]; i++) {
+            if ((lookup[field] & (1L << (i - MIN_VALUE[field]))) != 0) {
+                if (!first) {
+                    buf.append(",");
+                } else {
+                    first = false;
+                }
+
+                buf.append(String.valueOf(i));
+            }
+        }
+
+        return buf.toString();
+    }
+
+    /**
+    * Makes a <code>ParseException</code>. The exception message is constructed by
+    * taking the given message parameter and appending the supplied character data
+    * to the end of it. for example, if <code>msg == "Invalid character
+    * encountered"</code> and <code>data == {'A','g','u','s','t'}</code>, the resultant
+    * error message would be <code>"Invalid character encountered [Agust]"</code>.
+    *
+    * @param msg       The error message
+    * @param data      The data that the message
+    * @param offset    The offset into the data where the error was encountered.
+    *
+    * @return a newly created <code>ParseException</code> object.
+    */
+    private ParseException makeParseException(String msg, char[] data, int offset) {
+        char[] buf = new char[msg.length() + data.length + 3];
+        int msgLen = msg.length();
+        System.arraycopy(msg.toCharArray(), 0, buf, 0, msgLen);
+        buf[msgLen] = ' ';
+        buf[msgLen + 1] = '[';
+        System.arraycopy(data, 0, buf, msgLen + 2, data.length);
+        buf[buf.length - 1] = ']';
+        return new ParseException(new String(buf), offset);
+    }
+}
+
+
+class ValueSet {
+    public int pos;
+    public int value;
+}
diff --git a/src/java/com/opensymphony/oscache/util/StringUtil.java b/src/java/com/opensymphony/oscache/util/StringUtil.java
new file mode 100644
index 0000000..275e565
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/util/StringUtil.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides common utility methods for handling strings.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class StringUtil {
+    
+    private StringUtil() {
+    }
+	
+    /**
+     * Splits a string into substrings based on the supplied delimiter
+     * character. Each extracted substring will be trimmed of leading
+     * and trailing whitespace.
+     *
+     * @param str The string to split
+     * @param delimiter The character that delimits the string
+     * @return A string array containing the resultant substrings
+     */
+    public static final List split(String str, char delimiter) {
+        // return no groups if we have an empty string
+        if ((str == null) || "".equals(str)) {
+            return new ArrayList();
+        }
+
+        ArrayList parts = new ArrayList();
+        int currentIndex;
+        int previousIndex = 0;
+
+        while ((currentIndex = str.indexOf(delimiter, previousIndex)) > 0) {
+            String part = str.substring(previousIndex, currentIndex).trim();
+            parts.add(part);
+            previousIndex = currentIndex + 1;
+        }
+
+        parts.add(str.substring(previousIndex, str.length()).trim());
+
+        return parts;
+    }
+    
+    /**
+     * @param s the string to be checked
+     * @return true if the string parameter contains at least one element 
+     */
+    public static final boolean hasLength(String s) {
+    	return (s != null) && (s.length() > 0);
+    }
+    
+    /**
+     * @param s the string to be checked
+     * @return true if the string parameter is null or doesn't contain any element
+     * @since 2.4 
+     */
+    public static final boolean isEmpty(String s) {
+        return (s == null) || (s.length() == 0);
+    }
+    
+}
diff --git a/src/java/com/opensymphony/oscache/util/package.html b/src/java/com/opensymphony/oscache/util/package.html
new file mode 100644
index 0000000..518a289
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/util/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides utility classes that perform fairly general-purpose functions and are required
+by OSCache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+  <li><a href="http://www.opensymphony.com/oscache/cron.jsp">Using Cron Expressions with OSCache</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/CacheContextListener.java b/src/java/com/opensymphony/oscache/web/CacheContextListener.java
new file mode 100644
index 0000000..fe55281
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/CacheContextListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * Class for a clean startup and shutdown of the ServletCacheAdministrator and its application scoped cache.
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public class CacheContextListener implements ServletContextListener {
+
+    /**
+     * This notification occurs when the webapp is ready to process requests.<p>
+     * We use this hook to cleanly start up the {@link ServletCacheAdministrator}
+     * and create the application scope cache (which will consequentially
+     * initialize any listeners configured for it that implement <code>LifecycleAware</code>.)<p>
+     *
+     * As of Servlet 2.4, this is guaranteed to be called before any Servlet.init()
+     * methods.
+     */
+    public void contextInitialized(ServletContextEvent servletContextEvent) {
+        ServletContext context = servletContextEvent.getServletContext();
+        ServletCacheAdministrator.getInstance(context);
+    }
+
+    /**
+     * This notification occurs when the servlet context is about to be shut down.
+     * We use this hook to cleanly shut down the cache.
+     */
+    public void contextDestroyed(ServletContextEvent servletContextEvent) {
+        ServletContext context = servletContextEvent.getServletContext();
+        ServletCacheAdministrator.destroyInstance(context);
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/ServletCache.java b/src/java/com/opensymphony/oscache/web/ServletCache.java
new file mode 100644
index 0000000..682e8bb
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/ServletCache.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.CacheEntry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+/**
+ * A simple extension of Cache that implements a session binding listener,
+ * and deletes it's entries when unbound
+ *
+ * @author        <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author        <a href="mailto:tgochenour at peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @version        $Revision: 339 $
+ */
+public final class ServletCache extends Cache implements HttpSessionBindingListener, Serializable {
+    private static transient final Log log = LogFactory.getLog(ServletCache.class);
+
+    /**
+     * The admin for this cache
+     */
+    private ServletCacheAdministrator admin;
+
+    /**
+     * The scope of that cache.
+     */
+    private int scope;
+
+    /**
+     * Create a new ServletCache
+     *
+     * @param admin The ServletCacheAdministrator to administer this ServletCache.
+     * @param scope The scope of all entries in this hashmap
+     */
+    public ServletCache(ServletCacheAdministrator admin, int scope) {
+        super(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+        setScope(scope);
+        this.admin = admin;
+    }
+
+    /**
+     * Create a new Cache
+     *
+     * @param admin The CacheAdministrator to administer this Cache.
+     * @param algorithmClass The class that implement an algorithm
+     * @param limit The maximum cache size in number of entries
+     * @param scope The cache scope
+     */
+    public ServletCache(ServletCacheAdministrator admin, String algorithmClass, int limit, int scope) {
+        super(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence(), admin.isBlocking(), algorithmClass, limit);
+        setScope(scope);
+        this.admin = admin;
+    }
+
+    /**
+     * Get the cache scope
+     *
+     * @return The cache scope
+     */
+    public int getScope() {
+        return scope;
+    }
+
+    private void setScope(int scope) {
+        this.scope = scope;
+    }
+
+    /**
+     * When this Cache is bound to the session, do nothing.
+     *
+     * @param event The SessionBindingEvent.
+     */
+    public void valueBound(HttpSessionBindingEvent event) {
+    }
+
+    /**
+     * When the users's session ends, all listeners are finalized and the
+     * session cache directory is deleted from disk.
+     *
+     * @param event The event that triggered this unbinding.
+     */
+    public void valueUnbound(HttpSessionBindingEvent event) {
+        if (log.isInfoEnabled()) {
+        	// CACHE-229: don't access the session's id, because this can throw an IllegalStateException
+            log.info("[Cache] Unbound from session " + event.getSession() + " using name " + event.getName());
+        }
+
+        admin.finalizeListeners(this);
+        clear();
+    }
+
+    /**
+     * Indicates whether or not the cache entry is stale. This overrides the
+     * {@link Cache#isStale(CacheEntry, int, String)} method to take into account any
+     * flushing that may have been applied to the scope that this cache belongs to.
+     *
+     * @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 that defines fixed expiry dates and/or
+     * times for this cache entry.
+     *
+     * @return <code>true</code> if the entry is stale, <code>false</code> otherwise.
+     */
+    protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
+        return super.isStale(cacheEntry, refreshPeriod, cronExpiry) || admin.isScopeFlushed(cacheEntry, scope);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/ServletCacheAdministrator.java b/src/java/com/opensymphony/oscache/web/ServletCacheAdministrator.java
new file mode 100644
index 0000000..f245814
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/ServletCacheAdministrator.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.opensymphony.oscache.base.*;
+import com.opensymphony.oscache.base.events.CacheEventListener;
+import com.opensymphony.oscache.base.events.ScopeEvent;
+import com.opensymphony.oscache.base.events.ScopeEventListener;
+import com.opensymphony.oscache.base.events.ScopeEventType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+import java.util.*;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * A ServletCacheAdministrator creates, flushes and administers the cache.
+ * <p>
+ * This is a "servlet Singleton". This means it's not a Singleton in the traditional sense,
+ * that is stored in a static instance. It's a Singleton _per web app context_.
+ * <p>
+ * Once created it manages the cache path on disk through the oscache.properties
+ * file, and also keeps track of the flush times.
+ *
+ * @author <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:tgochenour at peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @version        $Revision: 463 $
+ */
+public class ServletCacheAdministrator extends AbstractCacheAdministrator implements Serializable {
+    private static final transient Log log = LogFactory.getLog(ServletCacheAdministrator.class);
+
+    /**
+    * Constants for properties read/written from/to file
+    */
+    private final static String CACHE_USE_HOST_DOMAIN_KEY = "cache.use.host.domain.in.key";
+    private final static String CACHE_KEY_KEY = "cache.key";
+
+    /**
+    * The default cache key that is used to store the cache in context.
+    */
+    private final static String DEFAULT_CACHE_KEY = "__oscache_cache";
+
+    /**
+    * Constants for scope's name
+    */
+    public final static String SESSION_SCOPE_NAME = "session";
+    public final static String APPLICATION_SCOPE_NAME = "application";
+
+    /**
+    * The suffix added to the cache key used to store a 
+    * ServletCacheAdministrator will be stored in the ServletContext
+    */
+    private final static String CACHE_ADMINISTRATOR_KEY_SUFFIX = "_admin";
+
+    /**
+    * The key under which an array of all ServletCacheAdministrator objects 
+    * will be stored in the ServletContext
+    */
+    private final static String CACHE_ADMINISTRATORS_KEY = "__oscache_admins";
+
+    /**
+    * Key used to store the current scope in the configuration. This is a hack
+    * to let the scope information get passed through to the DiskPersistenceListener,
+    * and will be removed in a future release.
+    */
+    public final static String HASH_KEY_SCOPE = "scope";
+
+    /**
+    * Key used to store the current session ID in the configuration. This is a hack
+    * to let the scope information get passed through to the DiskPersistenceListener,
+    * and will be removed in a future release.
+    */
+    public final static String HASH_KEY_SESSION_ID = "sessionId";
+
+    /**
+    * Key used to store the servlet container temporary directory in the configuration.
+    * This is a hack to let the scope information get passed through to the
+    * DiskPersistenceListener, and will be removed in a future release.
+    */
+    public final static String HASH_KEY_CONTEXT_TMPDIR = "context.tempdir";
+
+    /**
+    * The string to use as a file separator.
+    */
+    private final static String FILE_SEPARATOR = "/";
+
+    /**
+    * The character to use as a file separator.
+    */
+    private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
+
+    /**
+    * Constant for Key generation.
+    */
+    private final static short AVERAGE_KEY_LENGTH = 30;
+
+    /**
+    * Usable caracters for key generation
+    */
+    private static final String m_strBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    /**
+    * Map containing the flush times of different scopes
+    */
+    private Map flushTimes;
+
+    /**
+     * Required so we can look up the app scope cache without forcing a session creation.
+     */
+    private transient ServletContext context;
+
+    /**
+    * Key to use for storing and retrieving Object in contexts (Servlet, session).
+    */
+    private String cacheKey;
+
+    /**
+    *  Set property cache.use.host.domain.in.key=true to add domain information to key
+    *  generation for hosting multiple sites.
+    */
+    private boolean useHostDomainInKey = false;
+
+    /**
+    *        Create the cache administrator.
+    *
+    *        This will reset all the flush times and load the properties file.
+    */
+    private ServletCacheAdministrator(ServletContext context, Properties p) {
+        super(p);
+        config.set(HASH_KEY_CONTEXT_TMPDIR, context.getAttribute("javax.servlet.context.tempdir"));
+
+        flushTimes = new HashMap();
+        initHostDomainInKey();
+        this.context = context;
+    }
+
+    /**
+    * Obtain an instance of the CacheAdministrator
+    *
+    * @param context The ServletContext that this CacheAdministrator is a Singleton under
+    * @return Returns the CacheAdministrator instance for this context
+    */
+    public static ServletCacheAdministrator getInstance(ServletContext context) {
+        return getInstance(context, null);
+    }
+
+    /**
+     * Obtain an instance of the CacheAdministrator for the specified key
+     *
+     * @param context The ServletContext that this CacheAdministrator is a Singleton under
+     * @param key the cachekey or admincachekey for the CacheAdministrator wanted
+     * @return Returns the CacheAdministrator instance for this context, or null if no
+     * CacheAdministrator exists with the key supplied
+     */
+     public static ServletCacheAdministrator getInstanceFromKey(ServletContext context, String key) {
+    	 // Note we do not bother to check if the key is null because it mustn't.
+         if (!key.endsWith(CACHE_ADMINISTRATOR_KEY_SUFFIX)) {
+        	 key = key + CACHE_ADMINISTRATOR_KEY_SUFFIX;
+         }
+         return (ServletCacheAdministrator) context.getAttribute(key);
+     }
+
+    /**
+    * Obtain an instance of the CacheAdministrator
+    *
+    * @param context The ServletContext that this CacheAdministrator is a Singleton under
+    * @param p the properties to use for the cache if the cache administrator has not been
+    * created yet. Once the administrator has been created, the properties parameter is
+    * ignored for all future invocations. If a null value is passed in, then the properties
+    * are loaded from the oscache.properties file in the classpath.
+    * @return Returns the CacheAdministrator instance for this context
+    */
+    public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p)
+    {
+    	String adminKey = null; 
+    	if (p!= null) {
+    		adminKey = p.getProperty(CACHE_KEY_KEY);
+    	}
+    	if (adminKey == null) {
+    		adminKey = DEFAULT_CACHE_KEY;
+    	}
+		adminKey += CACHE_ADMINISTRATOR_KEY_SUFFIX;
+
+        ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(adminKey);
+
+        // First time we need to create the administrator and store it in the
+        // servlet context
+        if (admin == null) {
+            admin = new ServletCacheAdministrator(context, p);
+            Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
+            if (admins == null) {
+            	admins = new HashMap();
+            }
+            admins.put(adminKey, admin);
+            context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
+            context.setAttribute(adminKey, admin);
+
+            if (log.isInfoEnabled()) {
+                log.info("Created new instance of ServletCacheAdministrator with key "+adminKey);
+            }
+
+            admin.getAppScopeCache(context);
+        }
+
+        if (admin.context == null) {
+            admin.context = context;
+        }
+
+        return admin;
+    }
+
+    /**
+    * Shuts down all servlet cache administrators. This should usually only 
+    * be called when the controlling application shuts down.
+    */
+    public static void destroyInstance(ServletContext context)
+    {
+        ServletCacheAdministrator admin;
+        Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
+        if (admins != null)
+        {
+        	Set keys = admins.keySet();
+        	Iterator it = keys.iterator();
+        	while (it.hasNext())
+        	{
+        		String adminKey = (String) it.next();
+        		admin = (ServletCacheAdministrator) admins.get( adminKey );
+        		if (admin != null)
+        		{
+                    // Finalize the application scope cache
+                    Cache cache = (Cache) context.getAttribute(admin.getCacheKey());
+                    if (cache != null) {
+                    	admin.finalizeListeners(cache);
+                        context.removeAttribute(admin.getCacheKey());
+                        context.removeAttribute(adminKey);
+                        cache = null;
+                        if (log.isInfoEnabled()) {
+                            log.info("Shut down the ServletCacheAdministrator "+adminKey);
+                        }
+                    }
+                    admin = null;
+        		}
+        	}
+        	context.removeAttribute(CACHE_ADMINISTRATORS_KEY);
+        }
+    }
+
+
+    /**
+    * Grabs the cache for the specified scope
+    *
+    * @param request The current request
+    * @param scope The scope of this cache (<code>PageContext.APPLICATION_SCOPE</code>
+    * or <code>PageContext.SESSION_SCOPE</code>)
+    * @return The cache
+    */
+    public Cache getCache(HttpServletRequest request, int scope) {
+        if (scope == PageContext.APPLICATION_SCOPE) {
+            return getAppScopeCache(context);
+        }
+
+        if (scope == PageContext.SESSION_SCOPE) {
+            return getSessionScopeCache(request.getSession(true));
+        }
+
+        throw new RuntimeException("The supplied scope value of " + scope + " is invalid. Acceptable values are PageContext.APPLICATION_SCOPE and PageContext.SESSION_SCOPE");
+    }
+
+    /**
+    * A convenience method to retrieve the application scope cache
+
+    * @param context the current <code>ServletContext</code>
+    * @return the application scope cache. If none is present, one will
+    * be created.
+    */
+    public Cache getAppScopeCache(ServletContext context) {
+        Cache cache;
+        Object obj = context.getAttribute(getCacheKey());
+
+        if ((obj == null) || !(obj instanceof Cache)) {
+            if (log.isInfoEnabled()) {
+                log.info("Created new application-scoped cache at key: " + getCacheKey());
+            }
+
+            cache = createCache(PageContext.APPLICATION_SCOPE, null);
+            context.setAttribute(getCacheKey(), cache);
+        } else {
+            cache = (Cache) obj;
+        }
+
+        return cache;
+    }
+
+    /**
+    * A convenience method to retrieve the session scope cache
+    *
+    * @param session the current <code>HttpSession</code>
+    * @return the session scope cache for this session. If none is present,
+    * one will be created.
+    */
+    public Cache getSessionScopeCache(HttpSession session) {
+        Cache cache;
+        Object obj = session.getAttribute(getCacheKey());
+
+        if ((obj == null) || !(obj instanceof Cache)) {
+            if (log.isInfoEnabled()) {
+                log.info("Created new session-scoped cache in session " + session.getId() + " at key: " + getCacheKey());
+            }
+
+            cache = createCache(PageContext.SESSION_SCOPE, session.getId());
+            session.setAttribute(getCacheKey(), cache);
+        } else {
+            cache = (Cache) obj;
+        }
+
+        return cache;
+    }
+
+    /**
+    * Get the cache key from the properties. Set it to a default value if it
+    * is not present in the properties
+    *
+    * @return The cache.key property or the DEFAULT_CACHE_KEY
+    */
+    public String getCacheKey() {
+        if (cacheKey == null) {
+            cacheKey = getProperty(CACHE_KEY_KEY);
+
+            if (cacheKey == null) {
+                cacheKey = DEFAULT_CACHE_KEY;
+            }
+        }
+
+        return cacheKey;
+    }
+
+    /**
+    * Set the flush time for a specific scope to a specific time
+    *
+    * @param date  The time to flush the scope
+    * @param scope The scope to be flushed
+    */
+    public void setFlushTime(Date date, int scope) {
+        if (log.isInfoEnabled()) {
+            log.info("Flushing scope " + scope + " at " + date);
+        }
+
+        synchronized (flushTimes) {
+            if (date != null) {
+                // Trigger a SCOPE_FLUSHED event
+                dispatchScopeEvent(ScopeEventType.SCOPE_FLUSHED, scope, date, null);
+                flushTimes.put(new Integer(scope), date);
+            } else {
+                logError("setFlushTime called with a null date.");
+                throw new IllegalArgumentException("setFlushTime called with a null date.");
+            }
+        }
+    }
+
+    /**
+    * Set the flush time for a specific scope to the current time.
+    *
+    * @param scope The scope to be flushed
+    */
+    public void setFlushTime(int scope) {
+        setFlushTime(new Date(), scope);
+    }
+
+    /**
+    *        Get the flush time for a particular scope.
+    *
+    *        @param        scope        The scope to get the flush time for.
+    *        @return A date representing the time this scope was last flushed.
+    *        Returns null if it has never been flushed.
+    */
+    public Date getFlushTime(int scope) {
+        synchronized (flushTimes) {
+            return (Date) flushTimes.get(new Integer(scope));
+        }
+    }
+
+    /**
+    * Retrieve an item from the cache
+    *
+    * @param scope The cache scope
+    * @param request The servlet request
+    * @param key The key of the object to retrieve
+    * @param refreshPeriod The time interval specifying if an entry needs refresh
+    * @return The requested object
+    * @throws NeedsRefreshException
+    */
+    public Object getFromCache(int scope, HttpServletRequest request, String key, int refreshPeriod) throws NeedsRefreshException {
+        Cache cache = getCache(request, scope);
+        key = this.generateEntryKey(key, request, scope);
+        return cache.getFromCache(key, refreshPeriod);
+    }
+
+    /**
+    * Checks if the given scope 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
+    * @param scope The scope we're checking
+    *
+    * @return Whether or not the scope has been flushed more recently than this cache entry was updated.
+    */
+    public boolean isScopeFlushed(CacheEntry cacheEntry, int scope) {
+        Date flushDateTime = getFlushTime(scope);
+
+        if (flushDateTime != null) {
+            long lastUpdate = cacheEntry.getLastUpdate();
+            return (flushDateTime.getTime() >= lastUpdate);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+    * Register a listener for Cache Map events.
+    *
+    * @param listener  The object that listens to events.
+    */
+    public void addScopeEventListener(ScopeEventListener listener) {
+        listenerList.add(ScopeEventListener.class, listener);
+    }
+
+    /**
+    * Cancels a pending cache update. This should only be called by a thread
+    * that received a {@link NeedsRefreshException} and was unable to generate
+    * some new cache content.
+    *
+    * @param scope The cache scope
+    * @param request The servlet request
+    * @param key The cache entry key to cancel the update of.
+    */
+    public void cancelUpdate(int scope, HttpServletRequest request, String key) {
+        Cache cache = getCache(request, scope);
+        key = this.generateEntryKey(key, request, scope);
+        cache.cancelUpdate(key);
+    }
+
+    /**
+    * Flush all scopes at a particular time
+    *
+    * @param date The time to flush the scope
+    */
+    public void flushAll(Date date) {
+        synchronized (flushTimes) {
+            setFlushTime(date, PageContext.APPLICATION_SCOPE);
+            setFlushTime(date, PageContext.SESSION_SCOPE);
+            setFlushTime(date, PageContext.REQUEST_SCOPE);
+            setFlushTime(date, PageContext.PAGE_SCOPE);
+        }
+
+        // Trigger a flushAll event
+        dispatchScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, -1, date, null);
+    }
+
+    /**
+    * Flush all scopes instantly.
+    */
+    public void flushAll() {
+        flushAll(new Date());
+    }
+
+    /**
+    * Generates a cache entry key.
+    *
+    * If the string key is not specified, the HTTP request URI and QueryString is used.
+    * Operating systems that have a filename limitation less than 255 or have
+    * filenames that are case insensitive may have issues with key generation where
+    * two distinct pages map to the same key.
+    * <p>
+    * POST Requests (which have no distinguishing
+    * query string) may also generate identical keys for what is actually different pages.
+    * In these cases, specify an explicit key attribute for the CacheTag.
+    *
+    * @param key The key entered by the user
+    * @param request The current request
+    * @param scope The scope this cache entry is under
+    * @return The generated cache key
+    */
+    public String generateEntryKey(String key, HttpServletRequest request, int scope) {
+        return generateEntryKey(key, request, scope, null, null);
+    }
+
+    /**
+    * Generates a cache entry key.
+    *
+    * If the string key is not specified, the HTTP request URI and QueryString is used.
+    * Operating systems that have a filename limitation less than 255 or have
+    * filenames that are case insensitive may have issues with key generation where
+    * two distinct pages map to the same key.
+    * <p>
+    * POST Requests (which have no distinguishing
+    * query string) may also generate identical keys for what is actually different pages.
+    * In these cases, specify an explicit key attribute for the CacheTag.
+    *
+    * @param key The key entered by the user
+    * @param request The current request
+    * @param scope The scope this cache entry is under
+    * @param language The ISO-639 language code to distinguish different pages in application scope
+    * @return The generated cache key
+    */
+    public String generateEntryKey(String key, HttpServletRequest request, int scope, String language) {
+        return generateEntryKey(key, request, scope, language, null);
+    }
+
+    /**
+    * Generates a cache entry key.
+    * <p>
+    * If the string key is not specified, the HTTP request URI and QueryString is used.
+    * Operating systems that have a filename limitation less than 255 or have
+    * filenames that are case insensitive may have issues with key generation where
+    * two distinct pages map to the same key.
+    * <p>
+    * POST Requests (which have no distinguishing
+    * query string) may also generate identical keys for what is actually different pages.
+    * In these cases, specify an explicit key attribute for the CacheTag.
+    *
+    * @param key The key entered by the user
+    * @param request The current request
+    * @param scope The scope this cache entry is under
+    * @param language The ISO-639 language code to distinguish different pages in application scope
+    * @param suffix The ability to put a suffix at the end of the key
+    * @return The generated cache key
+    */
+    public String generateEntryKey(String key, HttpServletRequest request, int scope, String language, String suffix) {
+        /**
+        * Used for generating cache entry keys.
+        */
+        StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
+
+        // Append the language if available
+        if (language != null) {
+            cBuffer.append(FILE_SEPARATOR).append(language);
+        }
+
+        // Servers for multiple host domains need this distinction in the key
+        if (useHostDomainInKey) {
+            cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
+        }
+
+        if (key != null) {
+            cBuffer.append(FILE_SEPARATOR).append(key);
+        } else {
+            String generatedKey = request.getRequestURI();
+
+            if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) {
+                cBuffer.append(FILE_SEPARATOR_CHAR);
+            }
+
+            cBuffer.append(generatedKey);
+            cBuffer.append("_").append(request.getMethod()).append("_");
+
+            generatedKey = getSortedQueryString(request);
+
+            if (generatedKey != null) {
+                try {
+                    java.security.MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+                    byte[] b = digest.digest(generatedKey.getBytes());
+                    cBuffer.append('_');
+
+                    // Base64 encoding allows for unwanted slash characters.
+                    cBuffer.append(toBase64(b).replace('/', '_'));
+                } catch (Exception e) {
+                    // Ignore query string
+                }
+            }
+        }
+
+        // Do we want a suffix
+        if ((suffix != null) && (suffix.length() > 0)) {
+            cBuffer.append(suffix);
+        }
+
+        return cBuffer.toString();
+    }
+
+    /**
+    * Creates a string that contains all of the request parameters and their
+    * values in a single string. This is very similar to
+    * <code>HttpServletRequest.getQueryString()</code> except the parameters are
+    * sorted by name, and if there is a <code>jsessionid</code> parameter it is
+    * filtered out.<p>
+    * If the request has no parameters, this method returns <code>null</code>.
+    */
+    protected String getSortedQueryString(HttpServletRequest request) {
+        Map paramMap = request.getParameterMap();
+
+        if (paramMap.isEmpty()) {
+            return null;
+        }
+
+        Set paramSet = new TreeMap(paramMap).entrySet();
+
+        StringBuffer buf = new StringBuffer();
+
+        boolean first = true;
+
+        for (Iterator it = paramSet.iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            String[] values = (String[]) entry.getValue();
+
+            for (int i = 0; i < values.length; i++) {
+                String key = (String) entry.getKey();
+
+                if ((key.length() != 10) || !"jsessionid".equals(key)) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        buf.append('&');
+                    }
+
+                    buf.append(key).append('=').append(values[i]);
+                }
+            }
+        }
+
+        // We get a 0 length buffer if the only parameter was a jsessionid
+        if (buf.length() == 0) {
+            return null;
+        } else {
+            return buf.toString();
+        }
+    }
+
+    /**
+    * Log error messages to commons logging.
+    *
+    * @param message   Message to log.
+    */
+    public void logError(String message) {
+        log.error("[oscache]: " + message);
+    }
+
+    /**
+    * Put an object in the cache. This should only be called by a thread
+    * that received a {@link NeedsRefreshException}. Using session scope
+    * the thread has to insure that the session wasn't invalidated in
+    * the meantime. CacheTag and CacheFilter guarantee that the same
+    * cache is used in cancelUpdate and getFromCache.
+    *
+    * @param scope The cache scope
+    * @param request The servlet request
+    * @param key The object key
+    * @param content The object to add
+    */
+    public void putInCache(int scope, HttpServletRequest request, String key, Object content) {
+        putInCache(scope, request, key, content, null);
+    }
+
+    /**
+    * Put an object in the cache. This should only be called by a thread
+    * that received a {@link NeedsRefreshException}. Using session scope
+    * the thread has to insure that the session wasn't invalidated in
+    * the meantime. CacheTag and CacheFilter guarantee that the same
+    * cache is used in cancelUpdate and getFromCache.
+    *
+    * @param scope The cache scope
+    * @param request The servlet request
+    * @param key The object key
+    * @param content The object to add
+    * @param policy The refresh policy
+    */
+    public void putInCache(int scope, HttpServletRequest request, String key, Object content, EntryRefreshPolicy policy) {
+        Cache cache = getCache(request, scope);
+        key = this.generateEntryKey(key, request, scope);
+        cache.putInCache(key, content, policy);
+    }
+
+    /**
+    * Sets the cache capacity (number of items). If the cache contains
+    * more than <code>capacity</code> items then items will be removed
+    * to bring the cache back down to the new size.
+    *
+    * @param scope The cache scope
+    * @param request The servlet request
+    * @param capacity The new capacity
+    */
+    public void setCacheCapacity(int scope, HttpServletRequest request, int capacity) {
+        setCacheCapacity(capacity);
+        getCache(request, scope).setCapacity(capacity);
+    }
+
+    /**
+    * Unregister a listener for Cache Map events.
+    *
+    * @param listener  The object that currently listens to events.
+    */
+    public void removeScopeEventListener(ScopeEventListener listener) {
+        listenerList.remove(ScopeEventListener.class, listener);
+    }
+
+    /**
+    * Finalizes all the listeners that are associated with the given cache object
+    */
+    protected void finalizeListeners(Cache cache) {
+        super.finalizeListeners(cache);
+    }
+
+    /**
+    * Convert a byte array into a Base64 string (as used in mime formats)
+    */
+    private static String toBase64(byte[] aValue) {
+        int byte1;
+        int byte2;
+        int byte3;
+        int iByteLen = aValue.length;
+        StringBuffer tt = new StringBuffer();
+
+        for (int i = 0; i < iByteLen; i += 3) {
+            boolean bByte2 = (i + 1) < iByteLen;
+            boolean bByte3 = (i + 2) < iByteLen;
+            byte1 = aValue[i] & 0xFF;
+            byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
+            byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
+
+            tt.append(m_strBase64Chars.charAt(byte1 / 4));
+            tt.append(m_strBase64Chars.charAt((byte2 / 16) + ((byte1 & 0x3) * 16)));
+            tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + ((byte2 & 0xF) * 4)) : '='));
+            tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
+        }
+
+        return tt.toString();
+    }
+
+    /**
+    * Create a cache
+    *
+    * @param scope The cache scope
+    * @param sessionId The sessionId for with the cache will be created
+    * @return A new cache
+    */
+    private ServletCache createCache(int scope, String sessionId) {
+        ServletCache newCache = new ServletCache(this, algorithmClass, cacheCapacity, scope);
+
+        // TODO - Fix me please!
+        // Hack! This is nasty - if two sessions are created within a short
+        // space of time it is possible they will end up with duplicate
+        // session IDs being passed to the DiskPersistenceListener!...
+        config.set(HASH_KEY_SCOPE, "" + scope);
+        config.set(HASH_KEY_SESSION_ID, sessionId);
+
+        newCache = (ServletCache) configureStandardListeners(newCache);
+
+        if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
+            // Add any event listeners that have been specified in the configuration
+            CacheEventListener[] listeners = getCacheEventListeners();
+
+            for (int i = 0; i < listeners.length; i++) {
+                if (listeners[i] instanceof ScopeEventListener) {
+                    newCache.addCacheEventListener(listeners[i]);
+                }
+            }
+        }
+
+        return newCache;
+    }
+
+    /**
+    * Dispatch a scope event to all registered listeners.
+    *
+    * @param eventType   The type of event
+    * @param scope       Scope that was flushed (Does not apply for FLUSH_ALL event)
+    * @param date        Date of flushing
+    * @param origin      The origin of the event
+    */
+    private void dispatchScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
+        // Create the event
+        ScopeEvent event = new ScopeEvent(eventType, scope, 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+1] instanceof ScopeEventListener) {
+                ((ScopeEventListener) listeners[i + 1]).scopeFlushed(event);
+            }
+        }
+    }
+
+    /**
+    *        Set property cache.use.host.domain.in.key=true to add domain information to key
+    *  generation for hosting multiple sites
+    */
+    private void initHostDomainInKey() {
+        String propStr = getProperty(CACHE_USE_HOST_DOMAIN_KEY);
+
+        useHostDomainInKey = "true".equalsIgnoreCase(propStr);
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/WebEntryRefreshPolicy.java b/src/java/com/opensymphony/oscache/web/WebEntryRefreshPolicy.java
new file mode 100644
index 0000000..5390604
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/WebEntryRefreshPolicy.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.opensymphony.oscache.base.EntryRefreshPolicy;
+
+/**
+ * Interface to implement an entry refresh policy.
+ * Specify the name of the implementing class using the refreshpolicyclass
+ * attribute of the cache tag. If any additional parameters are required,
+ * they should be supplied using the refreshpolicyparam attribute.<p>
+ *
+ * For example:
+ * <pre>
+ * <cache:cache key="mykey"
+ *              refreshpolicyclass="com.mycompany.cache.policy.MyRefreshPolicy"
+ *              refreshpolicyparam="...additional data...">
+       My cached content
+ * </cache:cache>
+ * </pre>
+ *
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public interface WebEntryRefreshPolicy extends EntryRefreshPolicy {
+    /**
+     * Initializes the refresh policy.
+     *
+     * @param key   The cache key that is being checked.
+     * @param param Any optional parameters that were supplied
+     */
+    public void init(String key, String param);
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/CacheFilter.java b/src/java/com/opensymphony/oscache/web/filter/CacheFilter.java
new file mode 100644
index 0000000..824ce75
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/CacheFilter.java
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.EntryRefreshPolicy;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+import com.opensymphony.oscache.util.ClassLoaderUtil;
+import com.opensymphony.oscache.util.StringUtil;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * CacheFilter is a filter that allows for server-side caching of post-processed servlet content.<p>
+ *
+ * It also gives great programatic control over refreshing, flushing and updating the cache.<p>
+ *
+ * @author <a href="mailto:sergek [ AT ] lokitech.com">Serge Knystautas</a>
+ * @author <a href="mailto:mike [ AT ] atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:ltorunski [ AT ] t-online.de">Lars Torunski</a>
+ * @version $Revision: 434 $
+ */
+public class CacheFilter implements Filter, ICacheKeyProvider, ICacheGroupsProvider {
+    // Header
+    public static final String HEADER_LAST_MODIFIED = "Last-Modified";
+    public static final String HEADER_CONTENT_TYPE = "Content-Type";
+    public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
+    public static final String HEADER_EXPIRES = "Expires";
+    public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+    public static final String HEADER_CACHE_CONTROL = "Cache-Control";
+    public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
+
+    // Fragment parameter
+    public static final int FRAGMENT_AUTODETECT = -1;
+    public static final int FRAGMENT_NO = 0;
+    public static final int FRAGMENT_YES = 1;
+    
+    // No cache parameter
+    public static final int NOCACHE_OFF = 0;
+    public static final int NOCACHE_SESSION_ID_IN_URL = 1;
+    
+    // Last Modified parameter
+    public static final long LAST_MODIFIED_OFF = 0;
+    public static final long LAST_MODIFIED_ON = 1;
+    public static final long LAST_MODIFIED_INITIAL = -1;
+    
+    // Expires parameter
+    public static final long EXPIRES_OFF = 0;
+    public static final long EXPIRES_ON = 1;
+    public static final long EXPIRES_TIME = -1;
+    
+    // Cache Control
+    public static final long MAX_AGE_NO_INIT = Long.MIN_VALUE;
+    public static final long MAX_AGE_TIME = Long.MAX_VALUE;
+
+    // request attribute to avoid reentrance
+    private final static String REQUEST_FILTERED = "__oscache_filtered__";
+    private String requestFiltered;
+
+    // the policy for the expires header
+    private EntryRefreshPolicy expiresRefreshPolicy;
+    
+    // the logger
+    private final Log log = LogFactory.getLog(this.getClass());
+
+    // filter variables
+    private FilterConfig config;
+    private ServletCacheAdministrator admin = null;
+    private int cacheScope = PageContext.APPLICATION_SCOPE; // filter scope - default is APPLICATION
+    private int fragment = FRAGMENT_AUTODETECT; // defines if this filter handles fragments of a page - default is auto detect
+    private int time = 60 * 60; // time before cache should be refreshed - default one hour (in seconds)
+    private String cron = null; // A cron expression that determines when this cached content will expire - default is null
+    private int nocache = NOCACHE_OFF; // defines special no cache option for the requests - default is off
+    private long lastModified = LAST_MODIFIED_INITIAL; // defines if the last-modified-header will be sent - default is intial setting
+    private long expires = EXPIRES_ON; // defines if the expires-header will be sent - default is on
+    private long cacheControlMaxAge = -60; // defines which max-age in Cache-Control to be set - default is 60 seconds for max-age
+    private ICacheKeyProvider cacheKeyProvider = this; // the provider of the cache key - default is the CacheFilter itselfs
+    private ICacheGroupsProvider cacheGroupsProvider = this; // the provider of the cache groups - default is the CacheFilter itselfs
+    private List disableCacheOnMethods = null; // caching can be disabled by defining the http methods - default is off
+
+    /**
+     * Filter clean-up
+     */
+    public void destroy() {
+        //Not much to do...
+    }
+
+    /**
+     * The doFilter call caches the response by wrapping the <code>HttpServletResponse</code>
+     * object so that the output stream can be caught. This works by splitting off the output
+     * stream into two with the {@link SplitServletOutputStream} class. One stream gets written
+     * out to the response as normal, the other is fed into a byte array inside a {@link ResponseContent}
+     * object.
+     *
+     * @param request The servlet request
+     * @param response The servlet response
+     * @param chain The filter chain
+     * @throws ServletException IOException
+     */
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
+        if (log.isInfoEnabled()) {
+            log.info("OSCache: filter in scope " + cacheScope);
+        }
+
+        // avoid reentrance (CACHE-128) and check if request is cacheable
+        if (isFilteredBefore(request) || !isCacheableInternal(request)) {
+            chain.doFilter(request, response);
+            return;
+        }
+        request.setAttribute(requestFiltered, Boolean.TRUE);
+
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+        // checks if the response will be a fragment of a page
+        boolean fragmentRequest = isFragment(httpRequest);
+
+        // avoid useless session creation for application scope pages (CACHE-129)
+        Cache cache;
+        if (cacheScope == PageContext.SESSION_SCOPE) {
+            cache = admin.getSessionScopeCache(httpRequest.getSession(true));
+        } else {
+            cache = admin.getAppScopeCache(config.getServletContext());
+        }
+
+        // generate the cache entry key
+        String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache);
+
+        try {
+            ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time, cron);
+
+            if (log.isInfoEnabled()) {
+                log.info("OSCache: Using cached entry for " + key);
+            }
+
+            boolean acceptsGZip = false;
+            if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
+                long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE); // will return -1 if no header...
+
+                // only reply with SC_NOT_MODIFIED
+                // if the client has already the newest page and the response isn't a fragment in a page 
+                if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) {
+                    ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    return;
+                }
+                
+                acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest); 
+            }
+
+            respContent.writeTo(response, fragmentRequest, acceptsGZip);
+            // acceptsGZip is used for performance reasons above; use the following line for CACHE-49
+            // respContent.writeTo(response, fragmentRequest, acceptsGZipEncoding(httpRequest));
+        } catch (NeedsRefreshException nre) {
+            boolean updateSucceeded = false;
+
+            try {
+                if (log.isInfoEnabled()) {
+                    log.info("OSCache: New cache entry, cache stale or cache scope flushed for " + key);
+                }
+
+                CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper((HttpServletResponse) response, fragmentRequest, time * 1000L, lastModified, expires, cacheControlMaxAge);
+                chain.doFilter(request, cacheResponse);
+                cacheResponse.flushBuffer();
+
+                // Only cache if the response is cacheable
+                if (isCacheableInternal(cacheResponse)) {
+                    // get the cache groups of the content
+                    String[] groups = cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
+                    // Store as the cache content the result of the response
+                    cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy, null);
+                    updateSucceeded = true;
+                    if (log.isInfoEnabled()) {
+                        log.info("OSCache: New entry added to the cache with key " + key);
+                    }
+                }
+            } finally {
+                if (!updateSucceeded) {
+                    cache.cancelUpdate(key);
+                }
+            }
+        }
+    }
+
+    /**
+     * Initialize the filter. This retrieves a {@link ServletCacheAdministrator}
+     * instance and configures the filter based on any initialization parameters.<p>
+     * The supported initialization parameters are:
+     * <ul>
+     * 
+     * <li><b>oscache-properties-file</b> - the properties file that contains the OSCache configuration
+     * options to be used by the Cache that this Filter should use.</li>
+     * 
+     * @param filterConfig The filter configuration
+     */
+    public void init(FilterConfig filterConfig) {
+        // Get whatever settings we want...
+        config = filterConfig;
+
+        log.info("OSCache: Initializing CacheFilter with filter name " + config.getFilterName());
+
+        // setting the request filter to avoid reentrance with the same filter
+        requestFiltered = REQUEST_FILTERED + config.getFilterName();
+        log.info("Request filter attribute is " + requestFiltered);
+
+    	// filter Properties file
+        Properties props = null;
+        try {
+            String propertiesfile = config.getInitParameter("oscache-properties-file");
+            
+            if (propertiesfile != null && propertiesfile.length() > 0) {
+            	props = Config.loadProperties(propertiesfile, "CacheFilter with filter name '" + config.getFilterName()+ "'");
+            }
+        } catch (Exception e) {
+            log.info("OSCache: Init parameter 'oscache-properties-file' not set, using default.");
+        }
+        admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props);
+
+        // filter parameter time
+        String timeParam = config.getInitParameter("time");
+        if (timeParam != null) {
+            try {
+                setTime(Integer.parseInt(timeParam));
+            } catch (NumberFormatException nfe) {
+                log.error("OSCache: Unexpected value for the init parameter 'time', defaulting to one hour. Message=" + nfe.getMessage());
+            }
+        }
+        
+        // filter parameter scope
+        String scopeParam = config.getInitParameter("scope");
+        if (scopeParam != null) {
+            if ("session".equalsIgnoreCase(scopeParam)) {
+                setCacheScope(PageContext.SESSION_SCOPE);
+            } else if ("application".equalsIgnoreCase(scopeParam)) {
+                setCacheScope(PageContext.APPLICATION_SCOPE);
+            } else {
+                log.error("OSCache: Wrong value '" + scopeParam + "' for init parameter 'scope', defaulting to 'application'.");
+            }
+            
+        }
+
+        // filter parameter cron
+        setCron(config.getInitParameter("cron"));
+
+        // filter parameter fragment
+        String fragmentParam = config.getInitParameter("fragment");
+        if (fragmentParam != null) {
+            if ("no".equalsIgnoreCase(fragmentParam)) {
+                setFragment(FRAGMENT_NO);
+            } else if ("yes".equalsIgnoreCase(fragmentParam)) {
+                setFragment(FRAGMENT_YES);
+            } else if ("auto".equalsIgnoreCase(fragmentParam)) {
+                setFragment(FRAGMENT_AUTODETECT);
+            } else {
+                log.error("OSCache: Wrong value '" + fragmentParam + "' for init parameter 'fragment', defaulting to 'auto detect'.");
+            }
+        }
+        
+        // filter parameter nocache
+        String nocacheParam = config.getInitParameter("nocache");
+        if (nocacheParam != null) {
+            if ("off".equalsIgnoreCase(nocacheParam)) {
+                nocache = NOCACHE_OFF;
+            } else if ("sessionIdInURL".equalsIgnoreCase(nocacheParam)) {
+                nocache = NOCACHE_SESSION_ID_IN_URL;
+            } else {
+                log.error("OSCache: Wrong value '" + nocacheParam + "' for init parameter 'nocache', defaulting to 'off'.");
+            }
+        }
+
+        // filter parameter last modified
+        String lastModifiedParam = config.getInitParameter("lastModified");
+        if (lastModifiedParam != null) {
+            if ("off".equalsIgnoreCase(lastModifiedParam)) {
+                lastModified = LAST_MODIFIED_OFF;
+            } else if ("on".equalsIgnoreCase(lastModifiedParam)) {
+                lastModified = LAST_MODIFIED_ON;
+            } else if ("initial".equalsIgnoreCase(lastModifiedParam)) {
+                lastModified = LAST_MODIFIED_INITIAL;
+            } else {
+                log.error("OSCache: Wrong value '" + lastModifiedParam + "' for init parameter 'lastModified', defaulting to 'initial'.");
+            }
+        }
+        
+        // filter parameter expires
+        String expiresParam = config.getInitParameter("expires");
+        if (expiresParam != null) {
+            if ("off".equalsIgnoreCase(expiresParam)) {
+                setExpires(EXPIRES_OFF);
+            } else if ("on".equalsIgnoreCase(expiresParam)) {
+                setExpires(EXPIRES_ON);
+            } else if ("time".equalsIgnoreCase(expiresParam)) {
+                setExpires(EXPIRES_TIME);
+            } else {
+                log.error("OSCache: Wrong value '" + expiresParam + "' for init parameter 'expires', defaulting to 'on'.");
+            }
+        }
+
+        // filter parameter Cache-Control
+        String cacheControlMaxAgeParam = config.getInitParameter("max-age");
+        if (cacheControlMaxAgeParam != null) {
+            if (cacheControlMaxAgeParam.equalsIgnoreCase("no init")) {
+                setCacheControlMaxAge(MAX_AGE_NO_INIT);
+            } else if (cacheControlMaxAgeParam.equalsIgnoreCase("time")) {
+                setCacheControlMaxAge(MAX_AGE_TIME);
+            } else {
+                try {
+                    setCacheControlMaxAge(Long.parseLong(cacheControlMaxAgeParam));
+                } catch (NumberFormatException nfe) {
+                    log.error("OSCache: Unexpected value for the init parameter 'max-age', defaulting to '60'. Message=" + nfe.getMessage());
+                }
+            }
+        }
+
+        // filter parameter ICacheKeyProvider
+        ICacheKeyProvider cacheKeyProviderParam = (ICacheKeyProvider)instantiateFromInitParam("ICacheKeyProvider", ICacheKeyProvider.class, this.getClass().getName());
+        if (cacheKeyProviderParam != null) {
+            setCacheKeyProvider(cacheKeyProviderParam);
+        }
+
+        // filter parameter ICacheGroupsProvider
+        ICacheGroupsProvider cacheGroupsProviderParam = (ICacheGroupsProvider)instantiateFromInitParam("ICacheGroupsProvider", ICacheGroupsProvider.class, this.getClass().getName());
+        if (cacheGroupsProviderParam != null) {
+            setCacheGroupsProvider(cacheGroupsProviderParam);
+        }
+        
+        // filter parameter EntryRefreshPolicy
+        EntryRefreshPolicy expiresRefreshPolicyParam = (EntryRefreshPolicy)instantiateFromInitParam("EntryRefreshPolicy", EntryRefreshPolicy.class, ExpiresRefreshPolicy.class.getName());
+        if (expiresRefreshPolicyParam != null) {
+            setExpiresRefreshPolicy(expiresRefreshPolicyParam);
+        } else {
+            // setting the refresh period for this cache filter
+            setExpiresRefreshPolicy(new ExpiresRefreshPolicy(time));
+        }
+        
+        // filter parameter scope
+        String disableCacheOnMethodsParam = config.getInitParameter("disableCacheOnMethods");
+        if (StringUtil.hasLength(disableCacheOnMethodsParam)) {
+            disableCacheOnMethods = StringUtil.split(disableCacheOnMethodsParam, ',');   
+            // log.error("OSCache: Wrong value '" + disableCacheOnMethodsParam + "' for init parameter 'disableCacheOnMethods', defaulting to 'null'.");
+        }
+
+    }
+
+    private Object instantiateFromInitParam(String classInitParam, Class interfaceClass, String defaultObjectName) {
+		String className = config.getInitParameter(classInitParam);
+		if (className != null) {
+            try {
+                Class clazz = ClassLoaderUtil.loadClass(className, this.getClass());
+                if (!interfaceClass.isAssignableFrom(clazz)) {
+                    log.error("OSCache: Specified class '" + className + "' does not implement" + interfaceClass.getName() + ". Using default " + defaultObjectName + ".");
+                    return null;
+                } else {
+                    return clazz.newInstance();
+                }
+            } catch (ClassNotFoundException e) {
+                log.error("OSCache: Class '" + className + "' not found. Defaulting to " + defaultObjectName + ".", e);
+            } catch (InstantiationException e) {
+                log.error("OSCache: Class '" + className + "' could not be instantiated because it is not a concrete class. Using default object " + defaultObjectName + ".", e);
+            } catch (IllegalAccessException e) {
+                log.error("OSCache: Class '"+ className+ "' could not be instantiated because it is not public. Using default object " + defaultObjectName + ".", e);
+            }
+		}
+        return null;
+	}
+    
+    /**
+     * {@link ICacheKeyProvider}
+     * @see com.opensymphony.oscache.web.filter.ICacheKeyProvider#createCacheKey(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
+     */
+    public String createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
+        return scAdmin.generateEntryKey(null, httpRequest, cacheScope);
+    }
+
+    /**
+     * {@link ICacheGroupsProvider}
+     * @see com.opensymphony.oscache.web.filter.ICacheGroupsProvider#createCacheGroups(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
+     */
+    public String[] createCacheGroups(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
+        return null;
+    }
+
+    /**
+     * Checks if the request is a fragment in a page.
+     *
+     * According to Java Servlet API 2.2 (8.2.1 Dispatching Requests, Included
+     * Request Parameters), when a servlet is being used from within an include,
+     * the attribute <code>javax.servlet.include.request_uri</code> is set.
+     * According to Java Servlet API 2.3 this is excepted for servlets obtained
+     * by using the getNamedDispatcher method.
+     *
+     * @param request the to be handled request
+     * @return true if the request is a fragment in a page
+     */
+    public boolean isFragment(HttpServletRequest request) {
+        if (fragment == FRAGMENT_AUTODETECT) {
+            return request.getAttribute("javax.servlet.include.request_uri") != null;
+        } else {
+            return (fragment == FRAGMENT_NO) ? false : true;
+        }
+    }
+
+    /**
+     * Checks if the request was filtered before, so
+     * guarantees to be executed once per request. You
+     * can override this methods to define a more specific
+     * behaviour.
+     *
+     * @param request checks if the request was filtered before.
+     * @return true if it is the first execution
+     */
+    public boolean isFilteredBefore(ServletRequest request) {
+        return request.getAttribute(requestFiltered) != null;
+    }
+
+    /*
+     * isCacheableInternal gurarantees that the log information is correct.
+     * 
+     * @param request The servlet request
+     * @return Returns a boolean indicating if the request can be cached or not.
+     */
+    private final boolean isCacheableInternal(ServletRequest request) {
+        final boolean cacheable = isCacheable(request);
+
+        if (log.isDebugEnabled()) {
+            log.debug("OSCache: the request " + ((cacheable) ? "is" : "is not") + " cachable.");
+        }
+        
+        return cacheable;
+    }
+
+    /**
+     * isCacheable is a method allowing a subclass to decide if a request is
+     * cachable or not.
+     * 
+     * @param request The servlet request
+     * @return Returns a boolean indicating if the request can be cached or not.
+     */
+    public boolean isCacheable(ServletRequest request) {
+        boolean cacheable = request instanceof HttpServletRequest;
+
+        if (cacheable) {
+            HttpServletRequest requestHttp = (HttpServletRequest) request;
+            // CACHE-272 don't cache special http request methods
+            if ((disableCacheOnMethods != null) && (disableCacheOnMethods.contains(requestHttp.getMethod()))) {
+                return false;
+            }
+            if (nocache == NOCACHE_SESSION_ID_IN_URL) { // don't cache requests if session id is in the URL
+                cacheable = !requestHttp.isRequestedSessionIdFromURL();
+            }
+        }
+
+        return cacheable;
+    }
+    
+    /*
+     * isCacheableInternal gurarantees that the log information is correct.
+     * 
+     * @param cacheResponse the HTTP servlet response
+     * @return Returns a boolean indicating if the response can be cached or not.
+     */
+    private final boolean isCacheableInternal(CacheHttpServletResponseWrapper cacheResponse) {
+        final boolean cacheable = isCacheable(cacheResponse);
+
+        if (log.isDebugEnabled()) {
+            log.debug("OSCache: the response " + ((cacheable) ? "is" : "is not") + " cachable.");
+        }
+        
+        return cacheable;
+    }
+
+    /**
+     * isCacheable is a method allowing subclass to decide if a response is
+     * cachable or not.
+     * 
+     * @param cacheResponse the HTTP servlet response
+     * @return Returns a boolean indicating if the response can be cached or not.
+     */
+    public boolean isCacheable(CacheHttpServletResponseWrapper cacheResponse) {
+        // TODO implement CACHE-137 here
+        // Only cache if the response was 200
+        return cacheResponse.getStatus() == HttpServletResponse.SC_OK;
+    }
+
+    /**
+     * Check if the client browser support gzip compression.
+     * 
+     * @param request the http request
+     * @return true if client browser supports GZIP
+     */
+    public boolean acceptsGZipEncoding(HttpServletRequest request) {
+        String acceptEncoding = request.getHeader(HEADER_ACCEPT_ENCODING);
+        return  (acceptEncoding != null) && (acceptEncoding.indexOf("gzip") != -1);
+    }
+
+    // ---------------------------------
+    // --- getter and setter methods ---
+    // ---------------------------------
+    
+    /**
+     * @return the max-age of the cache control
+     * @since 2.4
+     */
+    public long getCacheControlMaxAge() {
+        if ((cacheControlMaxAge == MAX_AGE_NO_INIT) || (cacheControlMaxAge == MAX_AGE_TIME)) {
+            return cacheControlMaxAge;
+        }
+        return - cacheControlMaxAge;
+    }
+
+    /**
+     * <b>max-age</b> - defines the cache control response header max-age. Acceptable values are
+     * <code>MAX_AGE_NO_INIT</code> for don't initializing the max-age cache control, 
+     * <code>MAX_AGE_TIME</code> the max-age information will be based on the time parameter and creation time of the content (expiration timestamp minus current timestamp), and
+     * <code>[positive integer]</code> value constant in seconds to be set in every response, the default value is 60.
+     * 
+     * @param cacheControlMaxAge the cacheControlMaxAge to set
+     * @since 2.4
+     */
+    public void setCacheControlMaxAge(long cacheControlMaxAge) {
+        if ((cacheControlMaxAge == MAX_AGE_NO_INIT) || (cacheControlMaxAge == MAX_AGE_TIME)) {
+            this.cacheControlMaxAge = cacheControlMaxAge;
+        } else if (cacheControlMaxAge >= 0) {
+            // declare the cache control as a constant
+            // TODO check if this value can be stored as a positive long without changing it
+            this.cacheControlMaxAge = - cacheControlMaxAge;
+        } else {
+            log.warn("OSCache: 'max-age' must be at least a positive integer, defaulting to '60'. ");
+            this.cacheControlMaxAge = -60;
+        }
+    }
+
+    /**
+     * @return the cacheGroupsProvider
+     * @since 2.4
+     */
+    public ICacheGroupsProvider getCacheGroupsProvider() {
+        return cacheGroupsProvider;
+    }
+
+    /**
+     * <b>ICacheGroupsProvider</b> - Class implementing the interface <code>ICacheGroupsProvider</code>.
+     * A developer can implement a method which provides cache groups based on the request, 
+     * the servlect cache administrator and cache. The parameter has to be not <code>null</code>.
+     *
+     * @param cacheGroupsProvider the cacheGroupsProvider to set
+     * @since 2.4
+     */
+    public void setCacheGroupsProvider(ICacheGroupsProvider cacheGroupsProvider) {
+        if (cacheGroupsProvider == null) throw new IllegalArgumentException("The ICacheGroupsProvider is null.");
+        this.cacheGroupsProvider = cacheGroupsProvider;
+    }
+
+    /**
+     * @return the cacheKeyProvider
+     * @since 2.4
+     */
+    public ICacheKeyProvider getCacheKeyProvider() {
+        return cacheKeyProvider;
+    }
+
+    /**
+     * <b>ICacheKeyProvider</b> - Class implementing the interface <code>ICacheKeyProvider</code>.
+     * A developer can implement a method which provides cache keys based on the request, 
+     * the servlect cache administrator and cache. The parameter has to be not <code>null</code>.
+     * 
+     * @param cacheKeyProvider the cacheKeyProvider to set
+     * @since 2.4
+     */
+    public void setCacheKeyProvider(ICacheKeyProvider cacheKeyProvider) {
+        if (cacheKeyProvider == null) throw new IllegalArgumentException("The ICacheKeyProvider is null.");
+        this.cacheKeyProvider = cacheKeyProvider;
+    }
+
+    /**
+     * Returns PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE.
+     * @return the cache scope
+     * @since 2.4
+     */
+    public int getCacheScope() {
+        return cacheScope;
+    }
+
+    /**
+     * <b>scope</b> - the default scope to cache content. Acceptable values
+     * are <code>PageContext.APPLICATION_SCOPE</code> (default) and <code>PageContext.SESSION_SCOPE</code>.
+     * 
+     * @param cacheScope the cacheScope to set
+     * @since 2.4
+     */
+    public void setCacheScope(int cacheScope) {
+        if ((cacheScope != PageContext.APPLICATION_SCOPE) && (cacheScope != PageContext.SESSION_SCOPE))
+            throw new IllegalArgumentException("Acceptable values for cache scope are PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE");
+        this.cacheScope = cacheScope;
+    }
+
+    /**
+     * @return the cron
+     * @since 2.4
+     */
+    public String getCron() {
+        return cron;
+    }
+
+    /**
+     * <b>cron</b> - defines an expression that determines when the page content will expire.
+     * This allows content to be expired at particular dates and/or times, rather than once 
+     * a cache entry reaches a certain age.
+     * 
+     * @param cron the cron to set
+     * @since 2.4
+     */
+    public void setCron(String cron) {
+        this.cron = cron;
+    }
+
+    /**
+     * @return the expires header
+     * @since 2.4
+     */
+    public long getExpires() {
+        return expires;
+    }
+
+    /**
+     * <b>expires</b> - defines if the expires header will be sent in the response. Acceptable values are
+     * <code>EXPIRES_OFF</code> for don't sending the header, even it is set in the filter chain, 
+     * <code>EXPIRES_ON</code> (default) for sending it if it is set in the filter chain and 
+     * <code>EXPIRES_TIME</code> the expires information will be intialized based on the time parameter and creation time of the content.
+     * 
+     * @param expires the expires to set
+     * @since 2.4
+     */
+    public void setExpires(long expires) {
+        if ((expires < EXPIRES_TIME) || (expires > EXPIRES_ON)) throw new IllegalArgumentException("Expires value out of range.");
+        this.expires = expires;
+    }
+
+    /**
+     * @return the expiresRefreshPolicy
+     * @since 2.4
+     */
+    public EntryRefreshPolicy getExpiresRefreshPolicy() {
+        return expiresRefreshPolicy;
+    }
+
+    /**
+     * <b>EntryRefreshPolicy</b> - Class implementing the interface <code>EntryRefreshPolicy</code>.
+     * A developer can implement a class which provides a different custom cache invalidation policy for a specific cache entry.
+     * If not specified, the default policy is timed entry expiry as specified with the <b>time</b> parameter described above. 
+     *
+     * @param expiresRefreshPolicy the expiresRefreshPolicy to set
+     * @since 2.4
+     */
+    public void setExpiresRefreshPolicy(EntryRefreshPolicy expiresRefreshPolicy) {
+        if (expiresRefreshPolicy == null) throw new IllegalArgumentException("The EntryRefreshPolicy is null.");
+        this.expiresRefreshPolicy = expiresRefreshPolicy;
+    }
+
+    /**
+     * @return the fragment
+     * @since 2.4
+     */
+    public int getFragment() {
+        return fragment;
+    }
+
+    /**
+     * <b>fragment</b> - defines if this filter handles fragments of a page. Acceptable values
+     * are <code>FRAGMENT_AUTODETECT</code> (default) for auto detect, <code>FRAGMENT_NO</code> and <code>FRAGMENT_YES</code>.
+     * 
+     * @param fragment the fragment to set
+     * @since 2.4
+     */
+    public void setFragment(int fragment) {
+        if ((fragment < FRAGMENT_AUTODETECT) || (fragment > FRAGMENT_YES)) throw new IllegalArgumentException("Fragment value out of range.");
+        this.fragment = fragment;
+    }
+
+    /**
+     * @return the lastModified
+     * @since 2.4
+     */
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    /**
+     * <b>lastModified</b> - defines if the last modified header will be sent in the response. Acceptable values are
+     * <code>LAST_MODIFIED_OFF</code> for don't sending the header, even it is set in the filter chain, 
+     * <code>LAST_MODIFIED_ON</code> for sending it if it is set in the filter chain and 
+     * <code>LAST_MODIFIED_INITIAL</code> (default) the last modified information will be set based on the current time and changes are allowed.
+     * 
+     * @param lastModified the lastModified to set
+     * @since 2.4
+     */
+    public void setLastModified(long lastModified) {
+        if ((lastModified < LAST_MODIFIED_INITIAL) || (lastModified > LAST_MODIFIED_ON)) throw new IllegalArgumentException("LastModified value out of range.");
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * @return the nocache
+     * @since 2.4
+     */
+    public int getNocache() {
+        return nocache;
+    }
+
+    /**
+     * <b>nocache</b> - defines which objects shouldn't be cached. Acceptable values
+     * are <code>NOCACHE_OFF</code> (default) and <code>NOCACHE_SESSION_ID_IN_URL</code> if the session id is
+     * contained in the URL.
+     * 
+     * @param nocache the nocache to set
+     * @since 2.4
+     */
+    public void setNocache(int nocache) {
+        if ((nocache < NOCACHE_OFF) || (nocache > NOCACHE_SESSION_ID_IN_URL)) throw new IllegalArgumentException("Nocache value out of range.");
+        this.nocache = nocache;
+    }
+
+    /**
+     * @return the time
+     * @since 2.4
+     */
+    public int getTime() {
+        return time;
+    }
+
+    /**
+     * <b>time</b> - the default time (in seconds) to cache content for. The default
+     * value is 3600 seconds (one hour). Specifying -1 (indefinite expiry) as the cache 
+     * time will ensure a content does not become stale until it is either explicitly 
+     * flushed or the expires refresh policy causes the entry to expire.
+     * 
+     * @param time the time to set
+     * @since 2.4
+     */
+    public void setTime(int time) {
+        this.time = time;
+        // check if ExpiresRefreshPolicy has to be reset
+        if (expiresRefreshPolicy instanceof ExpiresRefreshPolicy) {
+            ((ExpiresRefreshPolicy) expiresRefreshPolicy).setRefreshPeriod(time);
+        }
+    }
+
+    /**
+     * @link http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod()
+     * @return the list of http method names for which cacheing should be disabled
+     * @since 2.4
+     */
+    public List getDisableCacheOnMethods() {
+        return disableCacheOnMethods;
+    }
+
+    /**
+     * <b>disableCacheOnMethods</b> - Defines the http method name for which cacheing should be disabled.
+     * The default value is <code>null</code> for cacheing all requests without regarding the method name.
+     * @link http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod()
+     * @param disableCacheOnMethods the list of http method names for which cacheing should be disabled
+     * @since 2.4
+     */
+    public void setDisableCacheOnMethods(List disableCacheOnMethods) {
+        this.disableCacheOnMethods = disableCacheOnMethods;
+    }
+    
+    // TODO: check if getter/setter for oscache-properties-file is possible
+    
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/CacheHttpServletResponseWrapper.java b/src/java/com/opensymphony/oscache/web/filter/CacheHttpServletResponseWrapper.java
new file mode 100644
index 0000000..ac9c403
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/CacheHttpServletResponseWrapper.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/**
+ * CacheServletResponse is a serialized representation of a response
+ *
+ * @author  <a href="mailto:sergek at lokitech.com">Serge Knystautas</a>
+ * @version $Revision: 469 $
+ */
+public class CacheHttpServletResponseWrapper extends HttpServletResponseWrapper {
+    
+    private final Log log = LogFactory.getLog(this.getClass());
+
+    /**
+     * We cache the printWriter so we can maintain a single instance
+     * of it no matter how many times it is requested.
+     */
+    private PrintWriter cachedWriter = null;
+    private ResponseContent result = null;
+    private SplitServletOutputStream cacheOut = null;
+    private boolean fragment = false;
+    private int status = SC_OK;
+    private long expires = CacheFilter.EXPIRES_ON;
+    private long lastModified = CacheFilter.LAST_MODIFIED_INITIAL;
+    private long cacheControl = -60;
+
+    /**
+     * Constructor
+     *
+     * @param response The servlet response
+     */
+    public CacheHttpServletResponseWrapper(HttpServletResponse response) {
+        this(response, false, Long.MAX_VALUE, CacheFilter.EXPIRES_ON, CacheFilter.LAST_MODIFIED_INITIAL, -60);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param response The servlet response
+     * @param fragment true if the repsonse indicates that it is a fragement of a page
+     * @param time the refresh time in millis
+     * @param lastModified defines if last modified header will be send, @see CacheFilter
+     * @param expires defines if expires header will be send, @see CacheFilter
+     * @param cacheControl defines if cache control header will be send, @see CacheFilter
+     */
+    public CacheHttpServletResponseWrapper(HttpServletResponse response, boolean fragment, long time, long lastModified, long expires, long cacheControl) {
+        super(response);
+        result = new ResponseContent();
+        this.fragment = fragment;
+        this.expires = expires;
+        this.lastModified = lastModified;
+        this.cacheControl = cacheControl;
+        
+        // only set inital values for last modified and expires, when a complete page is cached
+        if (!fragment) {
+            // setting a default last modified value based on object creation and remove the millis
+            if (lastModified == CacheFilter.LAST_MODIFIED_INITIAL) {
+                long current = System.currentTimeMillis();
+                current = current - (current % 1000);
+                result.setLastModified(current);
+                super.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, result.getLastModified());
+            }
+            // setting the expires value
+            if (expires == CacheFilter.EXPIRES_TIME) {
+                result.setExpires(result.getLastModified() + time);
+                super.setDateHeader(CacheFilter.HEADER_EXPIRES, result.getExpires());
+            }
+            // setting the cache control with max-age 
+            if (this.cacheControl == CacheFilter.MAX_AGE_TIME) {
+                // set the count down
+                long maxAge = System.currentTimeMillis();
+                maxAge = maxAge - (maxAge % 1000) + time;
+                result.setMaxAge(maxAge);
+                super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + time / 1000);
+            } else if (this.cacheControl != CacheFilter.MAX_AGE_NO_INIT) {
+                result.setMaxAge(this.cacheControl);
+                super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-this.cacheControl));
+            } else if (this.cacheControl == CacheFilter.MAX_AGE_NO_INIT ) {
+                result.setMaxAge(this.cacheControl);
+            }
+        }
+    }
+
+    /**
+     * Get a response content
+     *
+     * @return The content
+     */
+    public ResponseContent getContent() {
+        // Flush the buffer
+        try {
+            flush();
+        } catch (IOException ignore) {
+        }
+        
+        // Create the byte array
+        result.commit();
+
+        // Return the result from this response
+        return result;
+    }
+
+    /**
+     * Set the content type
+     *
+     * @param value The content type
+     */
+    public void setContentType(String value) {
+        if (log.isDebugEnabled()) {
+            log.debug("ContentType: " + value);
+        }
+
+        super.setContentType(value);
+        result.setContentType(value);
+    }
+
+    /**
+     * Set the date of a header
+     *
+     * @param name The header name
+     * @param value The date
+     */
+    public void setDateHeader(String name, long value) {
+        if (log.isDebugEnabled()) {
+            log.debug("dateheader: " + name + ": " + value);
+        }
+
+        // only set the last modified value, if a complete page is cached
+        if ((lastModified != CacheFilter.LAST_MODIFIED_OFF) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
+            if (!fragment) {
+                result.setLastModified(value);
+            } // TODO should we return now by fragments to avoid putting the header to the response?
+        } 
+
+        // implement RFC 2616 14.21 Expires (without max-age)
+        if ((expires != CacheFilter.EXPIRES_OFF) && (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name))) {
+            if (!fragment) {
+                result.setExpires(value);
+            } // TODO should we return now by fragments to avoid putting the header to the response?
+        }
+
+        super.setDateHeader(name, value);
+    }
+
+    /**
+     * Add the date of a header
+     *
+     * @param name The header name
+     * @param value The date
+     */
+    public void addDateHeader(String name, long value) {
+        if (log.isDebugEnabled()) {
+            log.debug("dateheader: " + name + ": " + value);
+        }
+
+        // only set the last modified value, if a complete page is cached
+        if ((lastModified != CacheFilter.LAST_MODIFIED_OFF) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
+            if (!fragment) {
+                result.setLastModified(value);
+            } // TODO should we return now by fragments to avoid putting the header to the response?
+        } 
+
+        // implement RFC 2616 14.21 Expires (without max-age)
+        if ((expires != CacheFilter.EXPIRES_OFF) && (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name))) {
+            if (!fragment) {
+                result.setExpires(value);
+            } // TODO should we return now by fragments to avoid putting the header to the response?
+        } 
+
+        super.addDateHeader(name, value);
+    }
+
+    /**
+     * Set a header field
+     *
+     * @param name The header name
+     * @param value The header value
+     */
+    public void setHeader(String name, String value) {
+        if (log.isDebugEnabled()) {
+            log.debug("header: " + name + ": " + value);
+        }
+
+        if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
+            result.setContentType(value);
+        }
+
+        if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
+            result.setContentEncoding(value);
+        }
+
+        super.setHeader(name, value);
+    }
+
+    /**
+     * Add a header field
+     *
+     * @param name The header name
+     * @param value The header value
+     */
+    public void addHeader(String name, String value) {
+        if (log.isDebugEnabled()) {
+            log.debug("header: " + name + ": " + value);
+        }
+
+        if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
+            result.setContentType(value);
+        }
+
+        if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
+            result.setContentEncoding(value);
+        }
+
+        super.addHeader(name, value);
+    }
+
+    /**
+     * Set the int value of the header
+     *
+     * @param name The header name
+     * @param value The int value
+     */
+    public void setIntHeader(String name, int value) {
+        if (log.isDebugEnabled()) {
+            log.debug("intheader: " + name + ": " + value);
+        }
+
+        super.setIntHeader(name, value);
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void setStatus(int status) {
+        super.setStatus(status);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void sendError(int status, String string) throws IOException {
+        super.sendError(status, string);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void sendError(int status) throws IOException {
+        super.sendError(status);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void setStatus(int status, String string) {
+        super.setStatus(status, string);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void sendRedirect(String location) throws IOException {
+        this.status = SC_MOVED_TEMPORARILY;
+        super.sendRedirect(location);
+    }
+
+    /**
+     * Retrieves the captured HttpResponse status.
+     */
+    public int getStatus() {
+        return status;
+    }
+
+    /**
+     * Set the locale
+     *
+     * @param value The locale
+     */
+    public void setLocale(Locale value) {
+        super.setLocale(value);
+        result.setLocale(value);
+    }
+
+    /**
+     * Get an output stream
+     *
+     * @throws IOException
+     */
+    public ServletOutputStream getOutputStream() throws IOException {
+        // Pass this faked servlet output stream that captures what is sent
+        if (cacheOut == null) {
+            cacheOut = new SplitServletOutputStream(result.getOutputStream(), super.getOutputStream());
+        }
+
+        return cacheOut;
+    }
+
+    /**
+     * Get a print writer
+     *
+     * @throws IOException
+     */
+    public PrintWriter getWriter() throws IOException {
+        if (cachedWriter == null) {
+            String encoding = getCharacterEncoding();
+            if (encoding != null) {
+                cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding));
+            } else { // using the default character encoding
+                cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream()));
+            }
+        }
+
+        return cachedWriter;
+    }
+    
+    /**
+     * Flushes all streams.
+     * @throws IOException
+     */
+    private void flush() throws IOException {
+        if (cacheOut != null) {
+            cacheOut.flush();
+        }
+
+        if (cachedWriter != null) {
+            cachedWriter.flush();
+        }
+    }
+
+    public void flushBuffer() throws IOException {
+        super.flushBuffer();
+        flush();
+    }
+
+    /**
+     * Returns a boolean indicating if the response has been committed. 
+     * A commited response has already had its status code and headers written.
+     * 
+     * @see javax.servlet.ServletResponseWrapper#isCommitted()
+     */
+    public boolean isCommitted() {
+        return super.isCommitted(); // || (result.getOutputStream() == null);
+    }
+
+    /**
+     * Clears any data that exists in the buffer as well as the status code and headers.
+     * If the response has been committed, this method throws an IllegalStateException.
+     * @see javax.servlet.ServletResponseWrapper#reset()
+     */
+    public void reset() {
+        log.info("CacheHttpServletResponseWrapper:reset()");
+        super.reset();
+        /*
+        cachedWriter = null;
+        result = new ResponseContent();
+        cacheOut = null;
+        fragment = false;
+        status = SC_OK;
+        expires = CacheFilter.EXPIRES_ON;
+        lastModified = CacheFilter.LAST_MODIFIED_INITIAL;
+        cacheControl = -60;
+        // time ?
+        */
+    }
+
+    /**
+     * Clears the content of the underlying buffer in the response without clearing headers or status code. 
+     * If the response has been committed, this method throws an IllegalStateException.
+     * @see javax.servlet.ServletResponseWrapper#resetBuffer()
+     */
+    public void resetBuffer() {
+        log.info("CacheHttpServletResponseWrapper:resetBuffer()");
+        super.resetBuffer();
+        /*
+        //cachedWriter = null;
+        result = new ResponseContent();
+        //cacheOut = null;
+        //fragment = false;
+        */
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/ExpiresRefreshPolicy.java b/src/java/com/opensymphony/oscache/web/filter/ExpiresRefreshPolicy.java
new file mode 100644
index 0000000..7597ec3
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/ExpiresRefreshPolicy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.EntryRefreshPolicy;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+
+/**
+ * Checks if a cache filter entry has expired.
+ * This is useful when expires header are used in the response.
+ *
+ * @version $Revision: 411 $
+ * @author <a href="mailto:ltorunski [ AT ] t-online.de">Lars Torunski</a>
+ */
+public class ExpiresRefreshPolicy implements EntryRefreshPolicy {
+    
+    /** the refresh period (in milliseconds) of a certain cache filter*/
+    private long refreshPeriod;
+
+    /**
+     * Constructor ExpiresRefreshPolicy.
+     *
+     * @param refreshPeriod the refresh period in seconds
+     */
+    public ExpiresRefreshPolicy(int refreshPeriod) {
+        this.refreshPeriod = refreshPeriod * 1000L;
+    }
+    
+    /**
+     * Indicates whether the supplied <code>CacheEntry</code> needs to be refreshed.
+     * This will be called when retrieving an entry from the cache - if this method
+     * returns <code>true</code> then a <code>NeedsRefreshException</code> will be
+     * thrown.
+     *
+     * @param entry The cache entry which is ignored.
+     * @return <code>true</code> if the content needs refreshing, <code>false</code> otherwise.
+     *
+     * @see NeedsRefreshException
+     * @see CacheEntry
+     */
+    public boolean needsRefresh(CacheEntry entry) {
+        
+        long currentTimeMillis = System.currentTimeMillis();
+        
+        if ((refreshPeriod >= 0) && (currentTimeMillis >= (entry.getLastUpdate() + refreshPeriod))) {
+            return true;
+        } else if (entry.getContent() instanceof ResponseContent) {
+            ResponseContent responseContent = (ResponseContent) entry.getContent();
+            return currentTimeMillis >= responseContent.getExpires();
+        } else {
+            return false;
+        }
+        
+    }
+
+    /**
+     * @return the refreshPeriod in seconds
+     * @since 2.4
+     */
+    public long getRefreshPeriod() {
+        return refreshPeriod / 1000;
+    }
+
+    /**
+     * @param refreshPeriod the refresh period in seconds
+     * @since 2.4
+     */
+    public void setRefreshPeriod(long refreshPeriod) {
+        this.refreshPeriod = refreshPeriod * 1000L;
+    }
+    
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/ICacheGroupsProvider.java b/src/java/com/opensymphony/oscache/web/filter/ICacheGroupsProvider.java
new file mode 100644
index 0000000..f737e7d
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/ICacheGroupsProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+/**
+ * Provider interface for cache groups creation in CacheFilter. A developer can implement a method which provides
+ * cache groups based on the request, the servlet cache administrator and cache.
+ * 
+ * JIRA issue: http://jira.opensymphony.com/browse/CACHE-195
+ *
+ * @author <a href="mailto:ltorunski at t-online.de">Lars Torunski</a>
+ * @version $Revision: 276 $
+ */
+public interface ICacheGroupsProvider {
+
+    /**
+     * Creates the cache groups for the CacheFilter.
+     *
+     * @param httpRequest the http request.
+     * @param scAdmin the ServletCacheAdministrator of the cache
+     * @param cache the cache of the ServletCacheAdministrator
+     * @return the cache key
+     */
+    public String[] createCacheGroups(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache);
+
+}
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/filter/ICacheKeyProvider.java b/src/java/com/opensymphony/oscache/web/filter/ICacheKeyProvider.java
new file mode 100644
index 0000000..77888fa
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/ICacheKeyProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+/**
+ * Provider interface for cache key creation. A developer can implement a method which provides
+ * cache keys based on the request, the servlet cache administrator and cache.
+ * 
+ * JIRA issue: http://jira.opensymphony.com/browse/CACHE-179
+ *
+ * @author <a href="mailto:ltorunski at t-online.de">Lars Torunski</a>
+ * @version $Revision: 276 $
+ */
+public interface ICacheKeyProvider {
+
+    /**
+     * Creates the cache key for the CacheFilter.
+     *
+     * @param httpRequest the http request.
+     * @param scAdmin the ServletCacheAdministrator of the cache
+     * @param cache the cache of the ServletCacheAdministrator
+     * @return the cache key
+     */
+    public String createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache);
+
+}
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/filter/ResponseContent.java b/src/java/com/opensymphony/oscache/web/filter/ResponseContent.java
new file mode 100644
index 0000000..e9a0341
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/ResponseContent.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import java.io.*;
+
+import java.util.Locale;
+import java.util.zip.GZIPInputStream;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Holds the servlet response in a byte array so that it can be held
+ * in the cache (and, since this class is serializable, optionally
+ * persisted to disk).
+ *
+ * @version $Revision: 362 $
+ * @author  <a href="mailto:sergek at lokitech.com">Serge Knystautas</a>
+ */
+public class ResponseContent implements Serializable {
+    private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
+    private Locale locale = null;
+    private String contentEncoding = null;
+    private String contentType = null;
+    private byte[] content = null;
+    private long expires = Long.MAX_VALUE;
+    private long lastModified = -1;
+    private long maxAge = -60;
+
+    public String getContentType() {
+        return contentType;
+    }
+    
+    /**
+     * Set the content type. We capture this so that when we serve this
+     * data from cache, we can set the correct content type on the response.
+     */
+    public void setContentType(String value) {
+        contentType = value;
+    }
+
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    public void setLastModified(long value) {
+        lastModified = value;
+    }
+
+    public String getContentEncoding() {
+        return contentEncoding;
+    }
+
+    public void setContentEncoding(String contentEncoding) {
+        this.contentEncoding = contentEncoding;
+    }
+
+    /**
+     * Set the Locale. We capture this so that when we serve this data from
+     * cache, we can set the correct locale on the response.
+     */
+    public void setLocale(Locale value) {
+        locale = value;
+    }
+
+    /**
+     * @return the expires date and time in miliseconds when the content will be stale
+     */
+    public long getExpires() {
+        return expires;
+    }
+
+    /**
+     * Sets the expires date and time in miliseconds.
+     * @param value time in miliseconds when the content will expire
+     */
+    public void setExpires(long value) {
+        expires = value;
+    }
+
+	/**
+	 * Returns the max age of the content in miliseconds. If expires header and cache control are
+	 * enabled both, both will be equal. 
+	 * @return the max age of the content in miliseconds, if -1 max-age is disabled
+	 */
+	public long getMaxAge() {
+		return maxAge;
+	}
+
+	/**
+	 * Sets the max age date and time in miliseconds. If the parameter is -1, the max-age parameter
+	 * won't be set by default in the Cache-Control header.
+	 * @param value sets the intial
+	 */
+	public void setMaxAge(long value) {
+		maxAge = value;
+	}
+
+    /**
+     * Get an output stream. This is used by the {@link SplitServletOutputStream}
+     * to capture the original (uncached) response into a byte array.
+     * @return the original (uncached) response, returns null if response is already committed.
+     */
+    public OutputStream getOutputStream() {
+        return bout;
+    }
+
+    /**
+     * Gets the size of this cached content.
+     *
+     * @return The size of the content, in bytes. If no content
+     * exists, this method returns <code>-1</code>.
+     */
+    public int getSize() {
+        return (content != null) ? content.length : (-1);
+    }
+
+    /**
+     * Called once the response has been written in its entirety. This
+     * method commits the response output stream by converting the output
+     * stream into a byte array.
+     */
+    public void commit() {
+        if (bout != null) {
+            content = bout.toByteArray();
+            bout = null;
+        }
+    }
+
+    /**
+     * Writes this cached data out to the supplied <code>ServletResponse</code>.
+     *
+     * @param response The servlet response to output the cached content to.
+     * @throws IOException
+     */
+    public void writeTo(ServletResponse response) throws IOException {
+        writeTo(response, false, false);
+    }
+
+    /**
+     * Writes this cached data out to the supplied <code>ServletResponse</code>.
+     *
+     * @param response The servlet response to output the cached content to.
+     * @param fragment is true if this content a fragment or part of a page
+     * @param acceptsGZip is true if client browser supports gzip compression
+     * @throws IOException
+     */
+    public void writeTo(ServletResponse response, boolean fragment, boolean acceptsGZip) throws IOException {
+        //Send the content type and data to this response
+        if (contentType != null) {
+            response.setContentType(contentType);
+        }
+        
+        if (fragment) {
+            // Don't support gzip compression if the content is a fragment of a page
+            acceptsGZip = false;
+        } else {
+            // add special headers for a complete page
+            if (response instanceof HttpServletResponse) {
+                HttpServletResponse httpResponse = (HttpServletResponse) response;
+                
+                // add the last modified header
+                if (lastModified != -1) {
+                    httpResponse.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, lastModified);
+                }
+                
+                // add the expires header
+                if (expires != Long.MAX_VALUE) {
+                    httpResponse.setDateHeader(CacheFilter.HEADER_EXPIRES, expires);
+                }
+                
+                // add the cache-control header for max-age
+                if (maxAge == CacheFilter.MAX_AGE_NO_INIT || maxAge == CacheFilter.MAX_AGE_TIME) {
+                	// do nothing
+                } else if (maxAge > 0) { // set max-age based on life time
+                	long currentMaxAge = maxAge / 1000 - System.currentTimeMillis() / 1000;
+                	if (currentMaxAge < 0) {
+                		currentMaxAge = 0;
+                	}
+                	httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + currentMaxAge);
+                } else {
+                	httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-maxAge));
+                }
+                
+            }
+        }
+
+        if (locale != null) {
+            response.setLocale(locale);
+        }
+
+        OutputStream out = new BufferedOutputStream(response.getOutputStream());
+
+        if (isContentGZiped()) {
+            if (acceptsGZip) {
+                ((HttpServletResponse) response).addHeader(CacheFilter.HEADER_CONTENT_ENCODING, "gzip");
+                response.setContentLength(content.length);
+                out.write(content);
+            } else {
+                // client doesn't support, so we have to uncompress it
+                ByteArrayInputStream bais = new ByteArrayInputStream(content);
+                GZIPInputStream zis = new GZIPInputStream(bais);
+
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                int numBytesRead = 0;
+                byte[] tempBytes = new byte[4196];
+
+                while ((numBytesRead = zis.read(tempBytes, 0, tempBytes.length)) != -1) {
+                    baos.write(tempBytes, 0, numBytesRead);
+                }
+
+                byte[] result = baos.toByteArray();
+
+                response.setContentLength(result.length);
+                out.write(result);
+            }
+        } else {
+            // the content isn't compressed
+            // regardless if the client browser supports gzip we will just return the content
+            response.setContentLength(content.length);
+            out.write(content);
+        }
+        out.flush();
+    }
+    
+    
+    /**
+     * @return true if the content is GZIP compressed
+     */
+    public boolean isContentGZiped() {
+        return "gzip".equals(contentEncoding);
+    }
+
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/SplitServletOutputStream.java b/src/java/com/opensymphony/oscache/web/filter/SplitServletOutputStream.java
new file mode 100644
index 0000000..2fafdea
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/SplitServletOutputStream.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.filter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletOutputStream;
+
+/**
+ * Extends the base <code>ServletOutputStream</code> class so that
+ * the stream can be captured as it gets written. This is achieved
+ * by overriding the <code>write()</code> methods and outputting
+ * the data to two streams - the original stream and a secondary stream
+ * that is designed to capture the written data.
+ *
+ * @version $Revision: 393 $
+ * @author  <a href="mailto:sergek at lokitech.com">Serge Knystautas</a>
+ */
+public class SplitServletOutputStream extends ServletOutputStream {
+    OutputStream captureStream = null;
+    OutputStream passThroughStream = null;
+
+    /**
+     * Constructs a split output stream that both captures and passes through
+     * the servlet response.
+     *
+     * @param captureStream The stream that will be used to capture the data.
+     * @param passThroughStream The pass-through <code>ServletOutputStream</code>
+     * that will write the response to the client as originally intended.
+     */
+    public SplitServletOutputStream(OutputStream captureStream, OutputStream passThroughStream) {
+        this.captureStream = captureStream;
+        this.passThroughStream = passThroughStream;
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param value The int data to write.
+     * @throws IOException
+     */
+    public void write(int value) throws IOException {
+        captureStream.write(value);
+        passThroughStream.write(value);
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param value The bytes to write to the streams.
+     * @throws IOException
+     */
+    public void write(byte[] value) throws IOException {
+        captureStream.write(value);
+        passThroughStream.write(value);
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param b The bytes to write out to the streams.
+     * @param off The offset into the byte data where writing should begin.
+     * @param len The number of bytes to write.
+     * @throws IOException
+     */
+    public void write(byte[] b, int off, int len) throws IOException {
+        captureStream.write(b, off, len);
+        passThroughStream.write(b, off, len);
+    }
+
+    /** 
+     * Flushes both the output streams.
+     * @throws IOException
+     */    
+    public void flush() throws IOException {
+        super.flush();
+        captureStream.flush(); //why not?
+        passThroughStream.flush();
+    }    
+
+    /**
+     * Closes both the output streams.
+     * @throws IOException 
+     */    
+    public void close() throws IOException {
+        super.close();
+        captureStream.close();
+        passThroughStream.close();
+    }    
+
+}
diff --git a/src/java/com/opensymphony/oscache/web/filter/package.html b/src/java/com/opensymphony/oscache/web/filter/package.html
new file mode 100644
index 0000000..38dcfcb
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/filter/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the caching filter (and its support classes) that allows HTTP responses
+to be cached by OSCache.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+  <li><a href="http://www.opensymphony.com/oscache/filter.jsp">Caching Filter Documentation</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/package.html b/src/java/com/opensymphony/oscache/web/package.html
new file mode 100644
index 0000000..e1760cb
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides classes and interfaces that make up the base of OSCache's web application support.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/com/opensymphony/oscache/web/tag/CacheTag.java b/src/java/com/opensymphony/oscache/web/tag/CacheTag.java
new file mode 100644
index 0000000..52bb6d0
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/CacheTag.java
@@ -0,0 +1,824 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.tag;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+import com.opensymphony.oscache.util.StringUtil;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+import com.opensymphony.oscache.web.WebEntryRefreshPolicy;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.TryCatchFinally;
+
+/**
+ * CacheTag is a tag that allows for server-side caching of post-processed JSP content.<p>
+ *
+ * It also gives great programatic control over refreshing, flushing and updating the cache.<p>
+ *
+ * Usage Example:
+ * <pre><code>
+ *     <%@ taglib uri="oscache" prefix="cache" %>
+ *     <cache:cache key="mycache"
+ *                 scope="application"
+ *                 refresh="false"
+ *                 time="30">
+ *              jsp content here... refreshed every 30 seconds
+ *     </cache:cache>
+ * </code></pre>
+ *
+ * @author <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:tgochenour at peregrine.com">Todd Gochenour</a>
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ * @version        $Revision: 331 $
+ */
+public class CacheTag extends BodyTagSupport implements TryCatchFinally {
+    /**
+    * Constants for time computation
+    */
+    private final static int SECOND = 1;
+    private final static int MINUTE = 60 * SECOND;
+    private final static int HOUR = 60 * MINUTE;
+    private final static int DAY = 24 * HOUR;
+    private final static int WEEK = 7 * DAY;
+    private final static int MONTH = 30 * DAY;
+    private final static int YEAR = 365 * DAY;
+
+    /**
+    * The key under which the tag counter will be stored in the request
+    */
+    private final static String CACHE_TAG_COUNTER_KEY = "__oscache_tag_counter";
+
+    /**
+    * Constants for refresh time
+    */
+    final static private int ONE_MINUTE = 60;
+    final static private int ONE_HOUR = 60 * ONE_MINUTE;
+    final static private int DEFAULT_TIMEOUT = ONE_HOUR;
+    private static transient Log log = LogFactory.getLog(CacheTag.class);
+
+    /**
+    * Cache modes
+    */
+    final static private int SILENT_MODE = 1;
+
+    /**
+    * A flag to indicate whether a NeedsRefreshException was thrown and
+    * the update needs to be cancelled
+    */
+    boolean cancelUpdateRequired = false;
+    private Cache cache = null;
+
+    /**
+    * If no groups are specified, the cached content does not get put into any groups
+    */
+    private List groups = null;
+    private ServletCacheAdministrator admin = null;
+
+    /**
+    * The actual key to use. This is generated based on the supplied key, scope etc.
+    */
+    private String actualKey = null;
+
+    /**
+    * The content that was retrieved from cache
+    */
+    private String content = null;
+
+    /**
+    * The cron expression that is used to expire cache entries at specific dates and/or times.
+    */
+    private String cron = null;
+
+    /**
+    * if cache key is null, the request URI is used
+    */
+    private String key = null;
+
+    /**
+    *  The ISO-639 language code to distinguish different pages in application scope
+    */
+    private String language = null;
+
+    /**
+    * Class used to handle the refresh policy logic
+    */
+    private String refreshPolicyClass = null;
+
+    /**
+    * Parameters that will be passed to the init method of the
+    * refresh policy instance.
+    */
+    private String refreshPolicyParam = null;
+
+    /**
+    * Whether the cache should be refreshed instantly
+    */
+    private boolean refresh = false;
+
+    /**
+    * used for subtags to tell this tag that we should use the cached version
+    */
+    private boolean useBody = true;
+
+    /**
+    * The cache mode. Valid values are SILENT_MODE
+    */
+    private int mode = 0;
+
+    /**
+    * The cache scope to use
+    */
+    private int scope = PageContext.APPLICATION_SCOPE;
+
+    /**
+    * time (in seconds) before cache should be refreshed
+    */
+    private int time = DEFAULT_TIMEOUT;
+
+    /**
+    * Set the time this cache entry will be cached for. A date and/or time in
+    * either ISO-8601 format or a simple format can be specified. The acceptable
+    * syntax for the simple format can be any one of the following:
+    *
+    * <ul>
+    * <li>0 (seconds)
+    * <li>0s (seconds)
+    * <li>0m (minutes)
+    * <li>0h (hours)
+    * <li>0d (days)
+    * <li>0w (weeks)
+    * </ul>
+    *
+    * @param duration The duration to cache this content (using either the simple
+    * or the ISO-8601 format). Passing in a duration of zero will turn off the
+    * caching, while a negative value will result in the cached content never
+    * expiring (ie, the cached content will always be served as long as it is
+    * present).
+    */
+    public void setDuration(String duration) {
+        try {
+            // Try Simple Date Format Duration first because it's faster
+            this.time = parseDuration(duration);
+        } catch (Exception ex) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed parsing simple duration format '" + duration + "' (" + ex.getMessage() + "). Trying ISO-8601 format...");
+            }
+
+            try {
+                // Try ISO-8601 Duration
+                this.time = parseISO_8601_Duration(duration);
+            } catch (Exception ex1) {
+                // An invalid duration entered, not much impact.
+                // The default timeout will be used
+                log.warn("The requested cache duration '" + duration + "' is invalid (" + ex1.getMessage() + "). Reverting to the default timeout");
+                this.time = DEFAULT_TIMEOUT;
+            }
+        }
+    }
+
+    /**
+    * Sets the cron expression that should be used to expire content at specific
+    * dates and/or times.
+    */
+    public void setCron(String cron) {
+        this.cron = cron;
+    }
+
+    /**
+     * Sets the groups for this cache entry. Any existing groups will
+     * be replaced.
+     *
+     * @param groups A comma-delimited list of groups that the cache entry belongs to.
+     */
+    public void setGroups(String groups) {
+    	// FIXME: ArrayList doesn't avoid duplicates
+        this.groups = StringUtil.split(groups, ',');
+    }
+
+    /**
+     * Adds to the groups for this cache entry.
+     *
+     * @param group A group to which the cache entry should belong.
+     */
+    void addGroup(String group) {
+        if (groups == null) {
+        	// FIXME: ArrayList doesn't avoid duplicates
+            groups = new ArrayList();
+        }
+
+        groups.add(group);
+    }
+
+    /**
+     * Adds comma-delimited list of groups that the cache entry belongs to.
+     *
+     * @param groups A comma-delimited list of groups that the cache entry belongs to also.
+     */
+    void addGroups(String groupsString) {
+        if (groups == null) {
+        	// FIXME: ArrayList doesn't avoid duplicates
+            groups = new ArrayList();
+        }
+
+        groups.addAll(StringUtil.split(groupsString, ','));
+    }
+
+    /**
+    * Set the key for this cache entry.
+    *
+    * @param key The key for this cache entry.
+    */
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    /**
+    * Set the ISO-639 language code to distinguish different pages in application scope
+    *
+    * @param language The language code for this cache entry.
+    */
+    public void setLanguage(String language) {
+        this.language = language;
+    }
+
+    /**
+    * This method allows the user to programatically decide whether the cached
+    * content should be refreshed immediately.
+    *
+    * @param refresh Whether or not to refresh this cache entry immediately.
+    */
+    public void setRefresh(boolean refresh) {
+        this.refresh = refresh;
+    }
+
+    /**
+    * Setting this to <code>true</code> prevents the cache from writing any output
+    * to the response, however the JSP content is still cached as normal.
+    * @param mode The cache mode to use.
+    */
+    public void setMode(String mode) {
+        if ("silent".equalsIgnoreCase(mode)) {
+            this.mode = SILENT_MODE;
+        } else {
+            this.mode = 0;
+        }
+    }
+
+    /**
+    * Class used to handle the refresh policy logic
+    */
+    public void setRefreshpolicyclass(String refreshPolicyClass) {
+        this.refreshPolicyClass = refreshPolicyClass;
+    }
+
+    /**
+    * Parameters that will be passed to the init method of the
+    * refresh policy instance.
+    */
+    public void setRefreshpolicyparam(String refreshPolicyParam) {
+        this.refreshPolicyParam = refreshPolicyParam;
+    }
+
+    // ----------- setMethods ------------------------------------------------------
+
+    /**
+    * Set the scope of this cache.
+    * <p>
+    * @param scope The scope of this cache. Either "application" (default) or "session".
+    */
+    public void setScope(String scope) {
+        if (scope.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
+            this.scope = PageContext.SESSION_SCOPE;
+        } else {
+            this.scope = PageContext.APPLICATION_SCOPE;
+        }
+    }
+
+    /**
+    * Set the time this cache entry will be cached for (in seconds)
+    *
+    * @param time The time to cache this content (in seconds). Passing in
+    * a time of zero will turn off the caching. A negative value for the
+    * time will result in the cached content never expiring (ie, the cached
+    * content will always be served if it is present)
+    */
+    public void setTime(int time) {
+        this.time = time;
+    }
+
+    /**
+    * This controls whether or not the body of the tag is evaluated or used.<p>
+    *
+    * It is most often called by the <UseCached /> tag to tell this tag to
+    * use the cached content.
+    *
+    * @see UseCachedTag
+    * @param useBody Whether or not to use the cached content.
+    */
+    public void setUseBody(boolean useBody) {
+        if (log.isDebugEnabled()) {
+            log.debug("<cache>: Set useBody to " + useBody);
+        }
+
+        this.useBody = useBody;
+    }
+
+    /**
+    * After the cache body, either update the cache, serve new cached content or
+    *  indicate an error.
+    *
+    * @throws JspTagException The standard exception thrown.
+    * @return The standard BodyTag return.
+    */
+    public int doAfterBody() throws JspTagException {
+        String body = null;
+
+        try {
+            // if we have a body, and we have not been told to use the cached version
+            if ((bodyContent != null) && (useBody || (time == 0)) && ((body = bodyContent.getString()) != null)) {
+                if ((time != 0) || (refreshPolicyClass != null)) {
+                    // Instantiate custom refresh policy if needed
+                    WebEntryRefreshPolicy policy = null;
+
+                    if (refreshPolicyClass != null) {
+                        try {
+                            policy = (WebEntryRefreshPolicy) Class.forName(refreshPolicyClass).newInstance();
+                            policy.init(actualKey, refreshPolicyParam);
+                        } catch (Exception e) {
+                            if (log.isInfoEnabled()) {
+                                log.info("<cache>: Problem instantiating or initializing refresh policy : " + refreshPolicyClass);
+                            }
+                        }
+                    }
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("<cache>: Updating cache entry with new content : " + actualKey);
+                    }
+
+                    cancelUpdateRequired = false;
+
+                    if ((groups == null) || groups.isEmpty()) {
+                        cache.putInCache(actualKey, body, policy);
+                    } else {
+                        String[] groupArray = new String[groups.size()];
+                        groups.toArray(groupArray);
+                        cache.putInCache(actualKey, body, groupArray, policy, null);
+                    }
+                }
+            }
+            // otherwise if we have been told to use the cached content and we have cached content
+            else {
+                if (!useBody && (content != null)) {
+                    if (log.isInfoEnabled()) {
+                        log.info("<cache>: Using cached version as instructed, useBody = false : " + actualKey);
+                    }
+
+                    body = content;
+                }
+                // either the cached entry is blank and a subtag has said don't useBody, or body is null
+                else {
+                    if (log.isInfoEnabled()) {
+                        log.info("<cache>: Missing cached content : " + actualKey);
+                    }
+
+                    body = "Missing cached content";
+                }
+            }
+
+            // Only display anything if we're not running in silent mode
+            if (mode != SILENT_MODE) {
+                bodyContent.clearBody();
+                bodyContent.write(body);
+                bodyContent.writeOut(bodyContent.getEnclosingWriter());
+            }
+        } catch (java.io.IOException e) {
+            throw new JspTagException("IO Error: " + e.getMessage());
+        }
+
+        return SKIP_BODY;
+    }
+
+    public void doCatch(Throwable throwable) throws Throwable {
+        throw throwable;
+    }
+
+    /**
+    * The end tag - clean up variables used.
+    *
+    * @throws JspTagException The standard exception thrown.
+    * @return The standard BodyTag return.
+    */
+    public int doEndTag() throws JspTagException {
+        return EVAL_PAGE;
+    }
+
+    public void doFinally() {
+        if (cancelUpdateRequired && (actualKey != null)) {
+            cache.cancelUpdate(actualKey);
+        }
+        
+        // reset all states, CACHE-144
+        groups = null;
+        scope = PageContext.APPLICATION_SCOPE;
+        cron = null;
+        key = null;
+        language = null;
+        refreshPolicyClass = null;
+        refreshPolicyParam = null;
+        time = DEFAULT_TIMEOUT;
+        refresh = false;
+        mode = 0;
+    }
+
+    /**
+    * The start of the tag.
+    * <p>
+    * Grabs the administrator, the cache, the specific cache entry, then decides
+    * whether to refresh.
+    * <p>
+    * If no refresh is needed, this serves the cached content directly.
+    *
+    * @throws JspTagException The standard exception thrown.
+    * @return The standard doStartTag() return.
+    */
+    public int doStartTag() throws JspTagException {
+        cancelUpdateRequired = false;
+        useBody = true;
+        content = null;
+
+        // We can only skip the body if the cache has the data
+        int returnCode = EVAL_BODY_BUFFERED;
+
+        if (admin == null) {
+            admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext());
+        }
+
+        // Retrieve the cache
+        if (scope == PageContext.SESSION_SCOPE) {
+            cache = admin.getSessionScopeCache(((HttpServletRequest) pageContext.getRequest()).getSession(true));
+        } else {
+            cache = admin.getAppScopeCache(pageContext.getServletContext());
+        }
+
+        // This allows to have multiple cache tags on a single page without
+        // having to specify keys. However, nested cache tags are not supported.
+        // In that case you would have to supply a key.
+        String suffix = null;
+
+        if (key == null) {
+            synchronized (pageContext.getRequest()) {
+                Object o = pageContext.getRequest().getAttribute(CACHE_TAG_COUNTER_KEY);
+
+                if (o == null) {
+                    suffix = "1";
+                } else {
+                    suffix = Integer.toString(Integer.parseInt((String) o) + 1);
+                }
+            }
+
+            pageContext.getRequest().setAttribute(CACHE_TAG_COUNTER_KEY, suffix);
+        }
+
+        // Generate the actual cache key
+        actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), scope, language, suffix);
+
+        /*
+        if
+        - refresh is not set,
+        - the cacheEntry itself does not need to be refreshed before 'time' and
+        - the administrator has not had the cache entry's scope flushed
+
+        send out the cached version!
+        */
+        try {
+            if (refresh) {
+                // Force a refresh
+                content = (String) cache.getFromCache(actualKey, 0, cron);
+            } else {
+                // Use the specified refresh period
+                content = (String) cache.getFromCache(actualKey, time, cron);
+            }
+
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("<cache>: Using cached entry : " + actualKey);
+                }
+
+                // Ensure that the cache returns the data correctly. Else re-evaluate the body
+                if ((content != null)) {
+                    if (mode != SILENT_MODE) {
+                        pageContext.getOut().write(content);
+                    }
+
+                    returnCode = SKIP_BODY;
+                }
+            } catch (IOException e) {
+                throw new JspTagException("IO Exception: " + e.getMessage());
+            }
+        } catch (NeedsRefreshException nre) {
+            cancelUpdateRequired = true;
+            content = (String) nre.getCacheContent();
+        }
+
+        if (returnCode == EVAL_BODY_BUFFERED) {
+            if (log.isDebugEnabled()) {
+                log.debug("<cache>: Cached content not used: New cache entry, cache stale or scope flushed : " + actualKey);
+            }
+        }
+
+        return returnCode;
+    }
+
+    /**
+    * Convert a SimpleDateFormat string to seconds
+    * Acceptable format are :
+    * <ul>
+    * <li>0s (seconds)
+    * <li>0m (minute)
+    * <li>0h (hour)
+    * <li>0d (day)
+    * <li>0w (week)
+    * </ul>
+    * @param   duration The simple date time to parse
+    * @return  The value in seconds
+    */
+    private int parseDuration(String duration) {
+        int time = 0;
+
+        //Detect if the factor is specified
+        try {
+            time = Integer.parseInt(duration);
+        } catch (Exception ex) {
+            //Extract number and ajust this number with the time factor
+            for (int i = 0; i < duration.length(); i++) {
+                if (!Character.isDigit(duration.charAt(i))) {
+                    time = Integer.parseInt(duration.substring(0, i));
+
+                    switch ((int) duration.charAt(i)) {
+                        case (int) 's':
+                            time *= SECOND;
+                            break;
+                        case (int) 'm':
+                            time *= MINUTE;
+                            break;
+                        case (int) 'h':
+                            time *= HOUR;
+                            break;
+                        case (int) 'd':
+                            time *= DAY;
+                            break;
+                        case (int) 'w':
+                            time *= WEEK;
+                            break;
+                        default:
+                        //no defined use as is
+                    }
+
+                    break;
+                }
+
+                // if
+            }
+
+            // for
+        }
+
+        // catch
+        return time;
+    }
+
+    /**
+    * Parse an ISO-8601 format date and return it's value in seconds
+    *
+    * @param duration The ISO-8601 date
+    * @return The equivalent number of seconds
+    * @throws Exception
+    */
+    private int parseISO_8601_Duration(String duration) throws Exception {
+        int years = 0;
+        int months = 0;
+        int days = 0;
+        int hours = 0;
+        int mins = 0;
+        int secs = 0;
+
+        // If there is a negative sign, it must be first
+        // If it is present, we will ignore it
+        int index = duration.indexOf("-");
+
+        if (index > 0) {
+            throw new Exception("Invalid duration (- must be at the beginning)");
+        }
+
+        // First caracter must be P
+        String workValue = duration.substring(index + 1);
+
+        if (workValue.charAt(0) != 'P') {
+            throw new Exception("Invalid duration (P must be at the beginning)");
+        }
+
+        // Must contain a value
+        workValue = workValue.substring(1);
+
+        if (workValue.length() == 0) {
+            throw new Exception("Invalid duration (nothing specified)");
+        }
+
+        // Check if there is a T
+        index = workValue.indexOf('T');
+
+        String timeString = "";
+
+        if (index > 0) {
+            timeString = workValue.substring(index + 1);
+
+            // Time cannot be empty
+            if (timeString.equals("")) {
+                throw new Exception("Invalid duration (T with no time)");
+            }
+
+            workValue = workValue.substring(0, index);
+        } else if (index == 0) {
+            timeString = workValue.substring(1);
+            workValue = "";
+        }
+
+        if (!workValue.equals("")) {
+            validateDateFormat(workValue);
+
+            int yearIndex = workValue.indexOf('Y');
+            int monthIndex = workValue.indexOf('M');
+            int dayIndex = workValue.indexOf('D');
+
+            if ((yearIndex != -1) && (monthIndex != -1) && (yearIndex > monthIndex)) {
+                throw new Exception("Invalid duration (Date part not properly specified)");
+            }
+
+            if ((yearIndex != -1) && (dayIndex != -1) && (yearIndex > dayIndex)) {
+                throw new Exception("Invalid duration (Date part not properly specified)");
+            }
+
+            if ((dayIndex != -1) && (monthIndex != -1) && (monthIndex > dayIndex)) {
+                throw new Exception("Invalid duration (Date part not properly specified)");
+            }
+
+            if (yearIndex >= 0) {
+                years = (new Integer(workValue.substring(0, yearIndex))).intValue();
+            }
+
+            if (monthIndex >= 0) {
+                months = (new Integer(workValue.substring(yearIndex + 1, monthIndex))).intValue();
+            }
+
+            if (dayIndex >= 0) {
+                if (monthIndex >= 0) {
+                    days = (new Integer(workValue.substring(monthIndex + 1, dayIndex))).intValue();
+                } else {
+                    if (yearIndex >= 0) {
+                        days = (new Integer(workValue.substring(yearIndex + 1, dayIndex))).intValue();
+                    } else {
+                        days = (new Integer(workValue.substring(0, dayIndex))).intValue();
+                    }
+                }
+            }
+        }
+
+        if (!timeString.equals("")) {
+            validateHourFormat(timeString);
+
+            int hourIndex = timeString.indexOf('H');
+            int minuteIndex = timeString.indexOf('M');
+            int secondIndex = timeString.indexOf('S');
+
+            if ((hourIndex != -1) && (minuteIndex != -1) && (hourIndex > minuteIndex)) {
+                throw new Exception("Invalid duration (Time part not properly specified)");
+            }
+
+            if ((hourIndex != -1) && (secondIndex != -1) && (hourIndex > secondIndex)) {
+                throw new Exception("Invalid duration (Time part not properly specified)");
+            }
+
+            if ((secondIndex != -1) && (minuteIndex != -1) && (minuteIndex > secondIndex)) {
+                throw new Exception("Invalid duration (Time part not properly specified)");
+            }
+
+            if (hourIndex >= 0) {
+                hours = (new Integer(timeString.substring(0, hourIndex))).intValue();
+            }
+
+            if (minuteIndex >= 0) {
+                mins = (new Integer(timeString.substring(hourIndex + 1, minuteIndex))).intValue();
+            }
+
+            if (secondIndex >= 0) {
+                if (timeString.length() != (secondIndex + 1)) {
+                    throw new Exception("Invalid duration (Time part not properly specified)");
+                }
+
+                if (minuteIndex >= 0) {
+                    timeString = timeString.substring(minuteIndex + 1, timeString.length() - 1);
+                } else {
+                    if (hourIndex >= 0) {
+                        timeString = timeString.substring(hourIndex + 1, timeString.length() - 1);
+                    } else {
+                        timeString = timeString.substring(0, timeString.length() - 1);
+                    }
+                }
+
+                if (timeString.indexOf('.') == (timeString.length() - 1)) {
+                    throw new Exception("Invalid duration (Time part not properly specified)");
+                }
+
+                secs = (new Double(timeString)).intValue();
+            }
+        }
+
+        // Compute Value
+        return secs + (mins * MINUTE) + (hours * HOUR) + (days * DAY) + (months * MONTH) + (years * YEAR);
+    }
+
+    /**
+    * Validate the basic date format
+    *
+    * @param basicDate The string to validate
+    * @throws Exception
+    */
+    private void validateDateFormat(String basicDate) throws Exception {
+        int yearCounter = 0;
+        int monthCounter = 0;
+        int dayCounter = 0;
+
+        for (int counter = 0; counter < basicDate.length(); counter++) {
+            // Check if there's any other caracters than Y, M, D and numbers
+            if (!Character.isDigit(basicDate.charAt(counter)) && (basicDate.charAt(counter) != 'Y') && (basicDate.charAt(counter) != 'M') && (basicDate.charAt(counter) != 'D')) {
+                throw new Exception("Invalid duration (Date part not properly specified)");
+            }
+
+            // Check if the allowed caracters are present more than 1 time
+            if (basicDate.charAt(counter) == 'Y') {
+                yearCounter++;
+            }
+
+            if (basicDate.charAt(counter) == 'M') {
+                monthCounter++;
+            }
+
+            if (basicDate.charAt(counter) == 'D') {
+                dayCounter++;
+            }
+        }
+
+        if ((yearCounter > 1) || (monthCounter > 1) || (dayCounter > 1)) {
+            throw new Exception("Invalid duration (Date part not properly specified)");
+        }
+    }
+
+    /**
+    * Validate the basic hour format
+    *
+    * @param basicHour The string to validate
+    * @throws Exception
+    */
+    private void validateHourFormat(String basicHour) throws Exception {
+        int minuteCounter = 0;
+        int secondCounter = 0;
+        int hourCounter = 0;
+
+        for (int counter = 0; counter < basicHour.length(); counter++) {
+            if (!Character.isDigit(basicHour.charAt(counter)) && (basicHour.charAt(counter) != 'H') && (basicHour.charAt(counter) != 'M') && (basicHour.charAt(counter) != 'S') && (basicHour.charAt(counter) != '.')) {
+                throw new Exception("Invalid duration (Time part not properly specified)");
+            }
+
+            if (basicHour.charAt(counter) == 'H') {
+                hourCounter++;
+            }
+
+            if (basicHour.charAt(counter) == 'M') {
+                minuteCounter++;
+            }
+
+            if (basicHour.charAt(counter) == 'S') {
+                secondCounter++;
+            }
+        }
+
+        if ((hourCounter > 1) || (minuteCounter > 1) || (secondCounter > 1)) {
+            throw new Exception("Invalid duration (Time part not properly specified)");
+        }
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/tag/FlushTag.java b/src/java/com/opensymphony/oscache/web/tag/FlushTag.java
new file mode 100644
index 0000000..042e506
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/FlushTag.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.tag;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.web.ServletCacheAdministrator;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * FlushTag flushes caches created with <cache>.
+ *
+ * This tag provides programmatic control over when caches are flushed,
+ * and can flush all caches at once.<p>
+ *
+ * Usage Examples:
+ * <pre><code>
+ * <%@ taglib uri="oscache" prefix="cache" %>
+ * <cache:flush scope="application" />
+ * <cache:flush scope="session" key="foobar" />
+ * </code></pre>
+ *
+ * Note: If no scope is provided (or scope is  null), it will flush
+ * all caches globally - use with care!<p>
+ * <p>
+ * Flushing is done by setting an appropriate application level time,
+ * which <cache> always looks at before retrieving the cache.
+ * If this 'flush time' is > that cache's last update, it will refresh
+ * the cache.
+ * <p>
+ * As such caches are not all 'flushed', they are all marked
+ * to be refreshed at their next access. That is the only way that
+ * the content can still be available if the refresh fails.
+ *
+ * @author <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @version $Revision: 254 $
+ */
+public class FlushTag extends TagSupport {
+    ServletCacheAdministrator admin = null;
+
+    /**
+     * A cache group.
+     * If specified, all content in that group will be flushed
+     */
+    String group = null;
+
+    /**
+     * Tag key.
+     */
+    String key = null;
+
+    /**
+     * if pattern value is specified, all keys that contain the pattern are flushed.
+     */
+    String pattern = null;
+    String scope = null;
+    int cacheScope = -1;
+
+    /**
+     *  The ISO-639 language code to distinguish different pages in application scope.
+     */
+    private String language = null;
+
+    /**
+     * The group to be flushed.
+     * If specified, all cached content in the group will be flushed.
+     *
+     * @param group The name of the group to flush.
+     */
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    /**
+     * The key to be flushed.
+     * If specified, only one cache entry will be flushed.
+     *
+     * @param value The key of the specific entry to flush.
+     */
+    public void setKey(String value) {
+        this.key = value;
+    }
+
+    /**
+     * Set the ISO-639 language code to distinguish different pages in application scope.
+     *
+     * @param value The language code for this cache entry.
+     */
+    public void setLanguage(String value) {
+        this.language = value;
+    }
+
+    /**
+     *  The key pattern to be flushed.
+     * If specified, all entries that contain the pattern will be flushed.
+     *  @param value The key of the specific entry to flush.
+     */
+    public void setPattern(String value) {
+        this.pattern = value;
+    }
+
+    /**
+     * Set the scope of this flush.
+     *
+     * @param value The scope - either "application" (default) or "session".
+     */
+    public void setScope(String value) {
+        if (value != null) {
+            if (value.equalsIgnoreCase(ServletCacheAdministrator.SESSION_SCOPE_NAME)) {
+                cacheScope = PageContext.SESSION_SCOPE;
+            } else if (value.equalsIgnoreCase(ServletCacheAdministrator.APPLICATION_SCOPE_NAME)) {
+                cacheScope = PageContext.APPLICATION_SCOPE;
+            }
+        }
+    }
+
+    /**
+     * Process the start of the tag.
+     *
+     * @throws JspTagException The standard tag exception thrown.
+     * @return The standard Tag return.
+     */
+    public int doStartTag() throws JspTagException {
+        if (admin == null) {
+            admin = ServletCacheAdministrator.getInstance(pageContext.getServletContext());
+        }
+
+        if (group != null) // We're flushing a group
+         {
+            if (cacheScope >= 0) {
+                Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
+                cache.flushGroup(group);
+            } else {
+                throw new JspTagException("A cache group was specified for flushing, but the scope wasn't supplied or was invalid");
+            }
+        } else if (pattern != null) // We're flushing keys which contain the pattern
+         {
+            if (cacheScope >= 0) {
+                Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
+                cache.flushPattern(pattern);
+            } else {
+                throw new JspTagException("A pattern was specified for flushing, but the scope wasn't supplied or was invalid");
+            }
+        } else if (key == null) // we're flushing a whole scope
+         {
+            if (cacheScope >= 0) {
+                admin.setFlushTime(cacheScope);
+            } else {
+                admin.flushAll();
+            }
+        } else // we're flushing just one key
+         {
+            if (cacheScope >= 0) {
+                String actualKey = admin.generateEntryKey(key, (HttpServletRequest) pageContext.getRequest(), cacheScope, language);
+
+                Cache cache = admin.getCache((HttpServletRequest) pageContext.getRequest(), cacheScope);
+                cache.flushEntry(actualKey);
+            } else {
+                throw new JspTagException("A cache key was specified for flushing, but the scope wasn't supplied or was invalid");
+            }
+        }
+
+        return SKIP_BODY;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/tag/GroupTag.java b/src/java/com/opensymphony/oscache/web/tag/GroupTag.java
new file mode 100644
index 0000000..db1ae3b
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/GroupTag.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.tag;
+
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * GroupTag is a tag that adds a group to an ancestor CacheTag's groups.<p>
+ *
+ * @author <a href="mailto:robvdv at yahoo.com">Robert van der Vliet</a>
+ */
+public class GroupTag extends TagSupport {
+    private Object group = null;
+
+    public int doStartTag() throws JspTagException {
+        CacheTag ancestorCacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
+
+        if (ancestorCacheTag == null) {
+            throw new JspTagException("GroupTag cannot be used from outside a CacheTag");
+        }
+
+        ancestorCacheTag.addGroup(String.valueOf(group));
+        return EVAL_BODY_INCLUDE;
+    }
+
+    public void setGroup(Object group) {
+        this.group = group;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/tag/GroupsTag.java b/src/java/com/opensymphony/oscache/web/tag/GroupsTag.java
new file mode 100644
index 0000000..0a8ffaa
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/GroupsTag.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.tag;
+
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * GroupsTag is a tag that add a comma-delimited list of groups to an ancestor CacheTag's groups.<p>
+ *
+ * @author <a href="mailto:ltorunski at t-online.de">Lars Torunski</a>
+ */
+public class GroupsTag extends TagSupport {
+    private Object groups = null;
+
+    public int doStartTag() throws JspTagException {
+        CacheTag ancestorCacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
+
+        if (ancestorCacheTag == null) {
+            throw new JspTagException("GroupsTag cannot be used from outside a CacheTag");
+        }
+
+        ancestorCacheTag.addGroups(String.valueOf(groups));
+        
+        return EVAL_BODY_INCLUDE;
+    }
+
+    public void setGroups(Object groups) {
+        this.groups = groups;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/tag/UseCachedTag.java b/src/java/com/opensymphony/oscache/web/tag/UseCachedTag.java
new file mode 100644
index 0000000..a00a256
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/UseCachedTag.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web.tag;
+
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * UseCachedTag is a tag that tells a <cache> tag to reuse the cached body.<p>
+ *
+ * Usage Example:
+ * <pre><code>
+ *       <%@ taglib uri="oscache" prefix="cache" %>
+ *       <cache:cache key="mycache" scope="application">
+ *                if (reuse cached)
+ *                        <cache:usecached />
+ *                else
+ *                        some other logic
+ *       </cache:cache>
+ * </code></pre>
+ *
+ * Note this is very useful with try / catch blocks
+ * so that you can still produce old cached data if an
+ * exception occurs, eg your database goes down.<p>
+ *
+ * @author        <a href="mailto:mike at atlassian.com">Mike Cannon-Brookes</a>
+ * @version        $Revision: 254 $
+ */
+public class UseCachedTag extends TagSupport {
+    boolean use = true;
+
+    /**
+     * Set the decision to use the body content of the ancestor <cache> or not.
+     *
+     * @param value Whether or not to use the body content. Default is true.
+     */
+    public void setUse(boolean value) {
+        this.use = value;
+    }
+
+    /**
+     * The start tag.
+     *
+     * @throws JspTagException The standard tag exception thrown.
+     * @return The standard Tag return.
+     */
+    public int doStartTag() throws JspTagException {
+        CacheTag cacheTag = (CacheTag) TagSupport.findAncestorWithClass(this, CacheTag.class);
+
+        if (cacheTag == null) {
+            throw new JspTagException("A UseCached tag must be nested within a Cache tag");
+        }
+
+        cacheTag.setUseBody(!use);
+
+        return SKIP_BODY;
+    }
+}
diff --git a/src/java/com/opensymphony/oscache/web/tag/package.html b/src/java/com/opensymphony/oscache/web/tag/package.html
new file mode 100644
index 0000000..509aec6
--- /dev/null
+++ b/src/java/com/opensymphony/oscache/web/tag/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+  Copyright (c) 2002-2003 by OpenSymphony
+  All rights reserved.
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides the tag libraries that allow OSCache to be accessed via JSP custom tags for
+caching portions of JSP pages.
+
+
+<h2>Package Specification</h2>
+
+<h2>Related Documentation</h2>
+
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.opensymphony.com/oscache">The OSCache Homepage</a>
+  <li><a href="http://www.opensymphony.com/oscache/tags.jsp">Tag Reference</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/java/overview.html b/src/java/overview.html
new file mode 100644
index 0000000..3be1bd6
--- /dev/null
+++ b/src/java/overview.html
@@ -0,0 +1,6 @@
+<body>
+
+This document is the API specification for OSCache - note, OSCache is built on top of
+<a href="http://wiki.opensymphony.com/display/CACHE" target="_blank">OSCache</a>.
+
+</body>
\ No newline at end of file
diff --git a/src/test/java/com/opensymphony/oscache/base/DummyAlwayRefreshEntryPolicy.java b/src/test/java/com/opensymphony/oscache/base/DummyAlwayRefreshEntryPolicy.java
new file mode 100644
index 0000000..0e72acc
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/DummyAlwayRefreshEntryPolicy.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+
+/**
+ * This is an dummy implementation of an EntryRefreshPolicy. It is just to
+ * illustrate how to use it.
+ *
+ * $Id: DummyAlwayRefreshEntryPolicy.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ */
+public final class DummyAlwayRefreshEntryPolicy implements EntryRefreshPolicy {
+    /**
+     * Dummy implementation of an entry refresh policy. A real implementation
+     * whould do some logic to determine if this entry needs to be refreshed.
+     * It can be calling a bean or checking some files, or even manually manage
+     * the time expiration.
+     *
+     * <p>
+     * @param entry  The entry for wich to determine if a refresh is needed
+     * @return True or false
+     */
+    public boolean needsRefresh(CacheEntry entry) {
+        return true;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/GroupConcurrencyProblemTestCase.java b/src/test/java/com/opensymphony/oscache/base/GroupConcurrencyProblemTestCase.java
new file mode 100644
index 0000000..6f6d771
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/GroupConcurrencyProblemTestCase.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.TestCase;
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 254 $
+ */
+public class GroupConcurrencyProblemTestCase extends TestCase {
+    private static GeneralCacheAdministrator cache = new GeneralCacheAdministrator();
+
+    public static void main(String[] args) {
+        System.out.println("START");
+
+        // Create some clients and start them running.
+        for (int i = 0; i < 100; i++) {
+            System.out.println("Creating thread: " + i);
+
+            new Client(i, cache).start();
+        }
+
+        System.out.println("END");
+    }
+}
+
+
+/* Inner class to hammer away at the cache. */
+class Client extends Thread {
+    private static final int MAX_ITERATIONS = 1000;
+    private GeneralCacheAdministrator cache;
+    private int id;
+
+    public Client(int newId, GeneralCacheAdministrator newCache) {
+        super();
+        id = newId;
+        cache = newCache;
+    }
+
+    public void run() {
+        for (int i = 0; i < MAX_ITERATIONS; i++) {
+            /* Put an entry from this Client into the shared group.
+             */
+            cache.putInCache(Integer.toString(id), "Some interesting data", new String[] {
+                    "GLOBAL_GROUP"
+                });
+
+            // Flush that group.
+            cache.flushGroup("GLOBAL_GROUP");
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestAbstractCacheAdministrator.java b/src/test/java/com/opensymphony/oscache/base/TestAbstractCacheAdministrator.java
new file mode 100644
index 0000000..f874326
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestAbstractCacheAdministrator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for the AbstractCacheAdministrator class. It tests some of the
+ * public methods of the admin. Some others cannot be tested since they are
+ * linked to the property file used for the tests, and since this file
+ * will change, the value of some parameters cannot be asserted
+ *
+ * $Id: TestAbstractCacheAdministrator.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public abstract class TestAbstractCacheAdministrator extends TestCase {
+    // Constants used in the tests
+    //private final String CACHE_PATH_PROP = "cache.path";
+    //private final String CONTENT = "Content for the abstract cache admin test";
+    //private final String ENTRY_KEY = "Test Abstract Admin Key";
+    private final String INVALID_PROP_NAME = "INVALID_PROP_NAME";
+    //private final String TEST_LOG = "test log";
+
+    /**
+     * Constructor for the this test class.
+     * <p>
+     * @param str Test name (required by JUnit)
+     */
+    protected TestAbstractCacheAdministrator(String str) {
+        super(str);
+    }
+
+    /**
+     * Cannot be tested since CacheContents is an interface
+     */
+    public void testCacheContents() {
+    }
+
+    /**
+     * We cannot test this method because the value depends on the property
+     */
+    public void testGetCachePath() {
+    }
+
+    /**
+     * Validate that the properties retrieved by the admin are the same as the one
+     * specified in the property file. Do not test cache path or memory cache
+     * since it changes with the tests
+     */
+    public void testGetProperty() {
+        // Check if all the default properties are OK
+        assertNull(getAdmin().getProperty(INVALID_PROP_NAME));
+        assertNull(getAdmin().getProperty(""));
+
+        try {
+            assertNull(getAdmin().getProperty(null));
+            fail("NullPointerException expected (property Key null).");
+        } catch (Exception e) {
+        }
+    }
+
+    /**
+     * We cannot test this method because the value depends on the property
+     */
+    public void testIsFileCaching() {
+    }
+
+    /**
+     * We cannot test this method because the value depends on the property
+     */
+    public void testIsMemoryCaching() {
+    }
+
+    /**
+     * Perform a call to the log method. Unfornately, there is no way to check
+     * if the logging is done correctly, we only invoke it
+     */
+    public void testLog() {
+        // Invoke the log
+        // The other log method is not tested since it calls the same as we do
+        //TODO
+
+        /*getAdmin().log(TEST_LOG, System.out);
+        getAdmin().log("", System.out);
+        getAdmin().log(null, System.out);
+        getAdmin().log(TEST_LOG, null);
+          */
+    }
+
+    // Abstract method that returns an instance of an admin
+    protected abstract AbstractCacheAdministrator getAdmin();
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestCache.java b/src/test/java/com/opensymphony/oscache/base/TestCache.java
new file mode 100644
index 0000000..52ad3e7
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestCache.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import java.util.Properties;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test the public methods of the Cache class
+ *
+ * $Id: TestCache.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestCache extends TestCase {
+    // Static variables required thru all the tests
+    private static Cache map = null;
+    private final String CONTENT = "Content for the cache test";
+
+    // Constants needed thru all the tests
+    private final String ENTRY_KEY = "Test cache key";
+    private final int NO_REFRESH_NEEDED = CacheEntry.INDEFINITE_EXPIRY;
+    private final int REFRESH_NEEDED = 0;
+
+    /**
+     * Class constructor.
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCache(String str) {
+        super(str);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a new Cache
+        if (map == null) {
+            GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+            map = admin.getCache();
+            assertNotNull(map);
+        }
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCache.class);
+    }
+
+    /**
+     * Verify that items may still be flushed by key pattern
+     */
+    public void testFlushPattern() {
+        // Try to flush with a bad pattern and ensure that our data is still there
+        map.putInCache(ENTRY_KEY, CONTENT);
+        map.flushPattern(ENTRY_KEY + "do not flush");
+        getBackContent(map, CONTENT, NO_REFRESH_NEEDED, false);
+
+        // Flush our map for real
+        map.flushPattern(ENTRY_KEY.substring(1, 2));
+        getBackContent(map, CONTENT, NO_REFRESH_NEEDED, true);
+
+        // Check invalid values
+        map.flushPattern("");
+        map.flushPattern(null);
+    }
+
+    /**
+     * Tests that with a very large amount of keys that added and trigger cache overflows, there is no memory leak
+     * @throws Exception
+     */
+    public void testBug174CacheOverflow() throws Exception {
+        
+        Properties p = new Properties();
+		p.setProperty(AbstractCacheAdministrator.CACHE_ALGORITHM_KEY, "com.opensymphony.oscache.base.algorithm.LRUCache");
+		p.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, "100");
+		GeneralCacheAdministrator admin = new GeneralCacheAdministrator(p);
+        
+        int cacheCapacity = 100;
+		int maxAddedCacheEntries = cacheCapacity*10;
+        String baseCacheKey= "baseKey";
+        String cacheValue ="same_value";
+
+		admin.setCacheCapacity(cacheCapacity);
+    	
+        Cache cache = admin.getCache();
+
+        //Add lots of different keys to trigger cache overflow
+		for (int keyIndex=0; keyIndex<maxAddedCacheEntries; keyIndex++) {
+			String key = baseCacheKey + keyIndex;
+			admin.putInCache(key, cacheValue);
+		}
+        
+		Assert.assertEquals("expected cache to be at its full capacity", cacheCapacity , cache.getSize());
+		Assert.assertTrue("expected cache overflows to have cleaned UpdateState instances. got [" + cache.getNbUpdateState() + "] updates while max is [" + cacheCapacity + "]", cache.getNbUpdateState() <= cacheCapacity);
+    }
+
+    /**
+     * Tests that with a very large amount of keys that added and trigger cache overflows, there is no memory leak
+     * @throws Exception
+     */
+    public void testBug174CacheOverflowAndUpdate() throws Exception {
+    	Properties p = new Properties();
+		p.setProperty(AbstractCacheAdministrator.CACHE_ALGORITHM_KEY, "com.opensymphony.oscache.base.algorithm.LRUCache");
+		p.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, "100");
+		GeneralCacheAdministrator admin = new GeneralCacheAdministrator(p);
+        
+        int cacheCapacity = 100;
+		int maxAddedCacheEntries = cacheCapacity*10;
+        String baseCacheKey= "baseKey";
+        String cacheValue ="same_value";
+
+		admin.setCacheCapacity(cacheCapacity);
+    	
+        Cache cache = admin.getCache();
+
+        
+        //Add lots of different keys to trigger cache overflow, mixed with updates
+		//FIXME: we may need different threads to enter branches recovering from current update.  
+		for (int keyIndex=0; keyIndex<maxAddedCacheEntries; keyIndex++) {
+			String key = baseCacheKey + keyIndex;
+			admin.putInCache(key, cacheValue);
+			
+			try {
+				admin.getFromCache(key, 0);
+				fail("expected element [" + key + "] not to be present");
+			} catch (NeedsRefreshException e) {
+				admin.putInCache(key, cacheValue);
+			}
+		}
+        
+		Assert.assertEquals("expected cache to be at its full capacity", cacheCapacity , cache.getSize());
+		Assert.assertTrue("expected cache overflows to have cleaned UpdateState instances. Nb states is:" + cache.getNbUpdateState() + " expected max="+ cacheCapacity, cache.getNbUpdateState() <= cacheCapacity);
+    }
+
+    
+    /**
+     * Tests that with a very large amount of keys accessed and cancelled, there is no memory leak
+     * @throws Exception
+     */
+    public void testBug174CacheMissesNonBlocking() throws Exception {
+    	testBug174CacheMisses(false);
+    }
+    
+    /**
+     * Tests that with a very large amount of keys accessed and cancelled, there is no memory leak
+     * @throws Exception
+     */
+    public void testBug174CacheMissesBlocking() throws Exception {
+    	testBug174CacheMisses(true);
+    }
+
+    /**
+     * Tests that with a very large amount of keys accessed and cancelled, there is no memory leak
+     * @throws Exception
+     */
+    public void testBug174CacheMisses(boolean block) throws Exception {
+    	Properties p = new Properties();
+		p.setProperty(AbstractCacheAdministrator.CACHE_ALGORITHM_KEY, "com.opensymphony.oscache.base.algorithm.LRUCache");
+		p.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, "100");
+		if (block) {
+			p.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "true");
+		}
+		GeneralCacheAdministrator admin = new GeneralCacheAdministrator(p);
+        
+        int cacheCapacity = 100;
+		int maxAddedCacheEntries = cacheCapacity*10;
+        String baseCacheKey= "baseKey";
+        //String cacheValue ="same_value";
+        
+		admin.setCacheCapacity(cacheCapacity);
+    	
+        Cache cache = admin.getCache();
+
+        //Access lots of different keys to trigger cache overflow
+		for (int keyIndex=0; keyIndex<maxAddedCacheEntries; keyIndex++) {
+			String key = baseCacheKey + keyIndex;
+			try {
+				admin.getFromCache(key);
+				fail("expected element [" + key + "] not to be present");
+			} catch (NeedsRefreshException e) {
+				admin.cancelUpdate(key);
+			}
+		}
+        
+		Assert.assertTrue("expected cache accesses to not leak past cache capacity. Nb states is:" + cache.getNbUpdateState() + " expected max="+ cacheCapacity, cache.getNbUpdateState() < cacheCapacity);
+    }
+    
+    /**
+     * Verify that we can put item in the cache and that they are correctly retrieved
+     */
+    public void testPutGetFromCache() {
+        // We put content in the cache and get it back with and without refresh
+        map.putInCache(ENTRY_KEY, CONTENT);
+        getBackContent(map, CONTENT, NO_REFRESH_NEEDED, false);
+        getBackContent(map, CONTENT, REFRESH_NEEDED, true);
+
+        // Test with invalid values
+
+        /** TODO Verify this logic */
+        try {
+            assertNull(map.getFromCache("", NO_REFRESH_NEEDED));
+        } catch (NeedsRefreshException nre) {
+            map.cancelUpdate("");
+        } catch (Exception e) {
+        }
+
+        try {
+            assertNull(map.getFromCache(null, NO_REFRESH_NEEDED));
+        } catch (NeedsRefreshException nre) {
+            map.cancelUpdate(null);
+        } catch (Exception e) {
+        }
+    }
+
+    /**
+     * Verify that we can put item in the cache and that they are correctly retrieved
+     */
+    public void testPutGetFromCacheWithPolicy() {
+        // We put content in the cache and get it back
+        map.putInCache(ENTRY_KEY + "policy", CONTENT, new DummyAlwayRefreshEntryPolicy());
+
+        // Should get a refresh
+        try {
+            map.getFromCache(ENTRY_KEY + "policy", -1);
+            fail("Should have got a refresh.");
+        } catch (NeedsRefreshException nre) {
+            map.cancelUpdate(ENTRY_KEY + "policy");
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        if (map != null) {
+            map.clear();
+        }
+    }
+
+    /**
+     * Retrieve the content in the cache
+     * <p>
+     * @param map       The Cache in which the data is stored
+     * @param content   The content expected to be retrieved
+     * @param refresh   Time interval to determine if the cache object needs refresh
+     * @param exceptionExpected Specify if a NeedsRefreshException is expected
+     */
+    private void getBackContent(Cache map, Object content, int refresh, boolean exceptionExpected) {
+        try {
+            assertEquals(content, map.getFromCache(ENTRY_KEY, refresh));
+
+            if (exceptionExpected) {
+                fail("NeedsRefreshException should have been thrown!");
+            }
+        } catch (NeedsRefreshException nre) {
+            map.cancelUpdate(ENTRY_KEY);
+
+            if (!exceptionExpected) {
+                fail("NeedsRefreshException shouldn't have been thrown!");
+            }
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestCacheEntry.java b/src/test/java/com/opensymphony/oscache/base/TestCacheEntry.java
new file mode 100644
index 0000000..b63a741
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestCacheEntry.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test the public methods of the CacheEntry class
+ *
+ * $Id: TestCacheEntry.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestCacheEntry extends TestCase {
+    // Static variables required thru the tests
+    static CacheEntry entry = null;
+    static long beforeCreation = 0;
+    static long afterCreation = 0;
+    private final String CONTENT = "Content for the cache entry test";
+
+    // Constants used thru the tests
+    private final String ENTRY_KEY = "Test cache entry key";
+    private final int NO_REFRESH_NEEDED = 1000000;
+    private final int REFRESH_NEEDED = 0;
+
+    /**
+     * Class constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheEntry(String str) {
+        super(str);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a cache entry object
+        if (entry == null) {
+            // Log the time before and after to verify the creation time
+            // in one of the tests
+            beforeCreation = System.currentTimeMillis();
+
+            entry = new CacheEntry(ENTRY_KEY);
+            afterCreation = System.currentTimeMillis();
+        }
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheEntry.class);
+    }
+
+    /**
+     * Verify the flush
+     */
+    public void testFlush() {
+        // Set the content so it shouldn't need refresh
+        entry.setContent(CONTENT);
+        assertTrue(!entry.needsRefresh(NO_REFRESH_NEEDED));
+
+        // Flush the entry. It should now needs refresh
+        entry.flush();
+        assertTrue(entry.needsRefresh(NO_REFRESH_NEEDED));
+    }
+
+    /**
+     * Verify that the creation time is correct
+     */
+    public void testGetCreated() {
+        assertBetweenOrEquals(beforeCreation, entry.getCreated(), afterCreation);
+    }
+
+    /**
+     * Retrieve the item created by the setup
+     */
+    public void testGetKey() {
+        assertTrue(entry.getKey().equals(ENTRY_KEY));
+    }
+
+    /**
+     * Verify that the last modification time is between the time before and
+     * after the alteration of the item
+     */
+    public void testGetLastUpdate() {
+        // again. Then we ensure that the update time is between our timestamps
+        long before = System.currentTimeMillis();
+        entry.setContent(CONTENT);
+
+        long after = System.currentTimeMillis();
+        assertBetweenOrEquals(before, entry.getLastUpdate(), after);
+    }
+
+    /**
+     * Verify that the "freshness detection" function properly
+     */
+    public void testNeedsRefresh() {
+        // Set the entry content so it shouldn't need refresh
+        // Invoke needsRefresh with no delay, so it should return true.
+        // Then invoke it with a big delay, so it should return false
+        assertTrue(entry.needsRefresh(REFRESH_NEEDED));
+        assertTrue(!entry.needsRefresh(NO_REFRESH_NEEDED));
+    }
+
+    /**
+     * Set the content of the item created by setup and then retrieve it and
+     * validate it
+     */
+    public void testSetGetContent() {
+        entry.setContent(CONTENT);
+        assertTrue(CONTENT.equals(entry.getContent()));
+
+        // Ensure that nulls are allowed
+        entry.setContent(null);
+        assertNull(entry.getContent());
+    }
+
+    /**
+     * Ensure that a value is between two others. Since the execution may be
+     * very fast, equals values are also considered to be between
+     */
+    private void assertBetweenOrEquals(long first, long between, long last) {
+        assertTrue(between >= first);
+        assertTrue(between <= last);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestCompleteBase.java b/src/test/java/com/opensymphony/oscache/base/TestCompleteBase.java
new file mode 100644
index 0000000..6e74060
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestCompleteBase.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.base.algorithm.TestCompleteAlgorithm;
+import com.opensymphony.oscache.base.events.TestCompleteEvents;
+import com.opensymphony.oscache.util.TestFastCronParser;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.base package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteBase.java 476 2007-07-01 10:35:29Z larst $
+ * @version        $Revision: 476 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteBase extends TestCase {
+    /**
+     * Constructor for the osCache project main test program
+     */
+    public TestCompleteBase(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteBase.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all base cache modules");
+        suite.addTest(TestFastCronParser.suite());
+        suite.addTest(TestCacheEntry.suite());
+        suite.addTest(TestCache.suite());
+        suite.addTest(TestConcurrency.suite());
+        suite.addTest(TestConcurrency2.suite());
+        suite.addTest(TestCompleteAlgorithm.suite());
+        suite.addTest(TestCompleteEvents.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestConcurrency.java b/src/test/java/com/opensymphony/oscache/base/TestConcurrency.java
new file mode 100644
index 0000000..053daac
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestConcurrency.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.BitSet;
+import java.util.Properties;
+
+/**
+ * Test the Cache class for any concurrency problems
+ *
+ * $Id: TestConcurrency.java 404 2007-02-24 10:21:00Z larst $
+ * @version        $Revision: 404 $
+ * @author <a href="mailto:chris at chris.com">Chris Miller</a>
+ */
+public class TestConcurrency extends TestCase {
+    private static transient final Log log = LogFactory.getLog(GeneralCacheAdministrator.class); //TestConcurrency.class
+
+    // Static variables required thru all the tests
+    private static GeneralCacheAdministrator admin = null;
+
+    // Constants needed in the tests
+    private final String KEY = "key";
+    private final String VALUE = "This is some content";
+    private final int ITERATION_COUNT = 5; //500;
+    private final int THREAD_COUNT = 6; //600;
+    private final int UNIQUE_KEYS = 1013;
+
+    /**
+     * Class constructor.
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestConcurrency(String str) {
+        super(str);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a new Cache
+        if (admin == null) {
+            Properties config = new Properties();
+            config.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, "70");
+            config.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "false");
+            admin = new GeneralCacheAdministrator();
+            assertNotNull(admin);
+        }
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestConcurrency.class);
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * new cache entry correctly
+     */
+    public void testNewEntry() {
+        String key = "new";
+
+        try {
+            admin.getFromCache(key, -1);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another couple of threads to get the same cache entry
+            GetEntry getEntry = new GetEntry(key, VALUE, -1, false);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+            getEntry = new GetEntry(key, VALUE, -1, false);
+            thread = new Thread(getEntry);
+            thread.start();
+
+            // OK, those threads should now be blocked waiting for the new cache
+            // entry to appear. Sleep for a bit to simulate the time taken to
+            // build the cache entry
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+            }
+
+            // Putting the entry in the cache should unblock the previous threads
+            admin.putInCache(key, VALUE);
+        }
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * new cache entry correctly
+     */
+    public void testNewEntryCancel() {
+        String key = "newCancel";
+        String NEW_VALUE = VALUE + "...";
+
+        try {
+            admin.getFromCache(key, -1);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry
+            GetEntry getEntry = new GetEntry(key, NEW_VALUE, -1, true);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // The above thread will be blocked waiting for the new content
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+            }
+
+            // Now cancel the update (eg because an exception occurred while building the content).
+            // This will unblock the other thread and it will receive a NeedsRefreshException.
+            admin.cancelUpdate(key);
+
+            // Wait a bit for the other thread to update the cache
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+            }
+
+            try {
+                Object newValue = admin.getFromCache(key, -1);
+                assertEquals(NEW_VALUE, newValue);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                fail("A NeedsRefreshException should not have been thrown");
+            }
+        }
+    }
+
+    /**
+     * Verify that we can concurrently access the cache without problems
+     */
+    public void testPut() {
+        Thread[] thread = new Thread[THREAD_COUNT];
+
+        for (int idx = 0; idx < THREAD_COUNT; idx++) {
+            OSGeneralTest runner = new OSGeneralTest();
+            thread[idx] = new Thread(runner);
+            thread[idx].start();
+        }
+
+        boolean stillAlive;
+
+        do {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+
+            stillAlive = false;
+
+            int i = 0;
+
+            while ((i < thread.length) && !stillAlive) {
+                stillAlive |= thread[i++].isAlive();
+            }
+        } while (stillAlive);
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * stale cache entry correctly
+     */
+    public void testStaleEntry() {
+        String key = "stale";
+        assertFalse("The cache should not be in blocking mode for this test.", admin.isBlocking());
+
+        admin.putInCache(key, VALUE);
+
+        try {
+            // This should throw a NeedsRefreshException since the refresh
+            // period is 0
+            admin.getFromCache(key, 0);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry.
+            // Since blocking mode is currently disabled we should
+            // immediately get back the stale entry
+            GetEntry getEntry = new GetEntry(key, VALUE, 0, false);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // Sleep for a bit to simulate the time taken to build the cache entry
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+            }
+
+            // Putting the entry in the cache should mean that threads now retrieve
+            // the updated entry
+            String newValue = "New value";
+            admin.putInCache(key, newValue);
+
+            getEntry = new GetEntry(key, newValue, -1, false);
+            thread = new Thread(getEntry);
+            thread.start();
+
+            try {
+                Object fromCache = admin.getFromCache(key, -1);
+                assertEquals(newValue, fromCache);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                fail("Should not have received a NeedsRefreshException");
+            }
+
+            // Give the GetEntry thread a chance to finish
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+            }
+        }
+    }
+
+    /**
+     * A test for the updating of a stale entry when CACHE.BLOCKING = TRUE
+     */
+    public void testStaleEntryBlocking() {
+        // A test for the case where oscache.blocking = true
+        admin.destroy();
+
+        Properties p = new Properties();
+        p.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "true");
+        admin = new GeneralCacheAdministrator(p);
+
+        assertTrue("The cache should be in blocking mode for this test.", admin.isBlocking());
+
+        // Use a unique key in case these test entries are being persisted
+        String key = "blocking";
+        String NEW_VALUE = VALUE + " abc";
+        admin.putInCache(key, VALUE);
+
+        try {
+            // Force a NeedsRefreshException
+            admin.getFromCache(key, 0);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry.
+            // Since blocking mode is enabled this thread should block
+            // until the entry has been updated.
+            GetEntry getEntry = new GetEntry(key, NEW_VALUE, 0, false);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // Sleep for a bit to simulate the time taken to build the cache entry
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+            }
+
+            // Putting the entry in the cache should mean that threads now retrieve
+            // the updated entry
+            admin.putInCache(key, NEW_VALUE);
+
+            getEntry = new GetEntry(key, NEW_VALUE, -1, false);
+            thread = new Thread(getEntry);
+            thread.start();
+
+            try {
+                Object fromCache = admin.getFromCache(key, -1);
+                assertEquals(NEW_VALUE, fromCache);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                fail("Should not have received a NeedsRefreshException");
+            }
+        }
+    }
+
+    /**
+     * Checks whether the cache handles simultaneous attempts to access a
+     * stable cache entry correctly when the blocking mode is enabled.
+     *
+     * Basically N threads are concurrently trying to access a same stale cache entry and each is cancelling its update. Each thread repeat this operation M times.
+     * The test is sucessfull if after some time, all threads are properly released
+     */
+    public void testConcurrentStaleGets() {
+        GeneralCacheAdministrator staticAdmin = admin;
+        admin = new GeneralCacheAdministrator(); //avoid poluting other test cases
+
+        try {
+            // A test for the case where oscache.blocking = true
+            //admin.destroy();
+            Properties p = new Properties();
+            p.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "true");
+            admin = new GeneralCacheAdministrator(p);
+
+            assertTrue("The cache should be in blocking mode for this test.", admin.isBlocking());
+
+            int nbThreads = 50;
+            int retryByThreads = 10000;
+
+            String key = "new";
+
+            //First put a value
+            admin.putInCache(key, VALUE);
+
+            try {
+                //Then test without concurrency that it is reported as stale when time-to-live is zero 
+                admin.getFromCache(key, 0);
+                fail("NeedsRefreshException should have been thrown");
+            } catch (NeedsRefreshException nre) {
+                //Ok this is was is excpected, we can release the update
+                admin.cancelUpdate(key);
+            }
+
+            //Then ask N threads to concurrently try to access this stale resource and each should receive a NeedsRefreshException, and cancel the update
+            Thread[] spawnedThreads = new Thread[nbThreads];
+            BitSet successfullThreadTerminations = new BitSet(nbThreads); //Track which thread successfully terminated
+
+            for (int threadIndex = 0; threadIndex < nbThreads; threadIndex++) {
+                GetStaleEntryAndCancelUpdate getEntry = new GetStaleEntryAndCancelUpdate(key, 0, retryByThreads, threadIndex, successfullThreadTerminations);
+                Thread thread = new Thread(getEntry);
+                spawnedThreads[threadIndex] = thread;
+                thread.start();
+            }
+
+            // OK, those threads should now repeatidely be blocked waiting for the new cache
+            // entry to appear. Wait for all of them to terminate
+            long maxWaitingSeconds = 100;
+            int maxWaitForEachThread = 5;
+            long waitStartTime = System.currentTimeMillis();
+
+            boolean atLeastOneThreadRunning = false;
+
+            while ((System.currentTimeMillis() - waitStartTime) < (maxWaitingSeconds * 1000)) {
+                atLeastOneThreadRunning = false;
+
+                //Wait a bit between each step to avoid consumming all CPU and preventing other threads from running.
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException ie) {
+                }
+
+                //check whether all threads are done.
+                for (int threadIndex = 0; threadIndex < nbThreads;
+                        threadIndex++) {
+                    Thread inspectedThread = spawnedThreads[threadIndex];
+
+                    try {
+                        inspectedThread.join(maxWaitForEachThread * 1000);
+                    } catch (InterruptedException e) {
+                        fail("Thread #" + threadIndex + " was interrupted");
+                    }
+
+                    if (inspectedThread.isAlive()) {
+                        atLeastOneThreadRunning = true;
+                        log.error("Thread #" + threadIndex + " did not complete within [" + ((System.currentTimeMillis() - waitStartTime) / 1000) + "] s ");
+                    }
+                }
+
+                if (!atLeastOneThreadRunning) {
+                    break; //while loop, test success.
+                }
+            }
+
+            assertTrue("at least one thread did not complete within [" + ((System.currentTimeMillis() - waitStartTime) / 1000) + "] s ", !atLeastOneThreadRunning);
+
+            for (int threadIndex = 0; threadIndex < nbThreads; threadIndex++) {
+                assertTrue("thread [" + threadIndex + "] did not successfully complete. ", successfullThreadTerminations.get(threadIndex));
+            }
+        } finally {
+            admin = staticAdmin;
+
+            //Avoid po
+        }
+    }
+
+    private class GetEntry implements Runnable {
+        String key;
+        String value;
+        boolean expectNRE;
+        int time;
+
+        GetEntry(String key, String value, int time, boolean expectNRE) {
+            this.key = key;
+            this.value = value;
+            this.time = time;
+            this.expectNRE = expectNRE;
+        }
+
+        public void run() {
+            try {
+                // Get from the cache
+                Object fromCache = admin.getFromCache(key, time);
+                assertEquals(value, fromCache);
+            } catch (NeedsRefreshException nre) {
+                if (!expectNRE) {
+                    admin.cancelUpdate(key);
+                    fail("Thread should have blocked until a new cache entry was ready");
+                } else {
+                    // Put a new piece of content into the cache
+                    admin.putInCache(key, value);
+                }
+            }
+        }
+    }
+
+    /**
+      * Basically requests a stale entry, expects to receive a NeedsRefreshException, and always cancels the update.
+      */
+    private class GetStaleEntryAndCancelUpdate implements Runnable {
+        String key;
+        int retries;
+        int time;
+        private final BitSet successfullThreadTerminations;
+        private final int threadIndex;
+
+        GetStaleEntryAndCancelUpdate(String key, int time, int retries, int threadIndex, BitSet successfullThreadTerminations) {
+            this.key = key;
+            this.time = time;
+            this.retries = retries;
+            this.threadIndex = threadIndex;
+            this.successfullThreadTerminations = successfullThreadTerminations;
+        }
+
+        public void run() {
+            for (int retryIndex = 0; retryIndex < retries; retryIndex++) {
+                try {
+                    // Get from the cache
+                    Object fromCache = admin.getFromCache(key, time);
+                    assertNull("Thread index [" + retryIndex + "] expected stale request [" + retryIndex + "] to be received, got [" + fromCache + "]", fromCache);
+                } catch (NeedsRefreshException nre) {
+                    try {
+                        admin.cancelUpdate(key);
+                    } catch (Throwable t) {
+                        log.error("Thread index [" + retryIndex + "]: Unexpectedly caught exception [" + t + "]", t);
+                        fail("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]");
+                    }
+                } catch (Throwable t) {
+                    log.error("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]", t);
+                    fail("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]");
+                }
+            }
+
+            //Once we successfully terminate, we update the corresponding bit to let the Junit know we succeeded.
+            synchronized (successfullThreadTerminations) {
+                successfullThreadTerminations.set(threadIndex);
+            }
+        }
+    }
+
+    private class OSGeneralTest implements Runnable {
+        public void doit(int i) {
+            int refreshPeriod = 500 /*millis*/;
+            String key = KEY + (i % UNIQUE_KEYS);
+            admin.putInCache(key, VALUE);
+
+            try {
+                // Get from the cache
+                admin.getFromCache(KEY, refreshPeriod);
+            } catch (NeedsRefreshException nre) {
+                // Get the value
+                // Store in the cache
+                admin.putInCache(KEY, VALUE);
+            }
+
+            // Flush occasionally
+            if ((i % (UNIQUE_KEYS + 1)) == 0) {
+                admin.getCache().flushEntry(key);
+            }
+        }
+
+        public void run() {
+            int start = (int) (Math.random() * UNIQUE_KEYS);
+            System.out.print(start + " ");
+
+            for (int i = start; i < (start + ITERATION_COUNT); i++) {
+                doit(i);
+            }
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/TestConcurrency2.java b/src/test/java/com/opensymphony/oscache/base/TestConcurrency2.java
new file mode 100644
index 0000000..1904bba
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/TestConcurrency2.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner;
+import net.sourceforge.groboutils.junit.v1.TestRunnable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Properties;
+
+/**
+ * Test the Cache class for any concurrency problems
+ *
+ * $Id: TestConcurrency.java 404 2007-02-24 10:21:00Z larst $
+ * @version        $Revision: 404 $
+ */
+public class TestConcurrency2 extends TestCase {
+
+    private static transient final Log log = LogFactory.getLog(GeneralCacheAdministrator.class); //TestConcurrency2.class
+
+    // Static variables required thru all the tests
+    private static GeneralCacheAdministrator admin = null;
+
+    // Constants needed in the tests
+    private final String KEY = "key";
+    private final String VALUE = "This is some content";
+    private final int ITERATION_COUNT = 1000;
+    private final int THREAD_COUNT = 3;
+    private final int UNIQUE_KEYS = 1013;
+
+    /**
+     * Class constructor.
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestConcurrency2(String str) {
+        super(str);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a new Cache
+        if (admin == null) {
+            Properties config = new Properties();
+            config.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, "70");
+            config.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "false");
+            admin = new GeneralCacheAdministrator(config);
+            assertNotNull(admin);
+        }
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestConcurrency2.class);
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * new cache entry correctly
+     */
+    public void testNewEntry() {
+        String key = "new";
+
+        try {
+            admin.getFromCache(key, -1);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another couple of threads to get the same cache entry
+            GetEntry getEntry1 = new GetEntry(key, VALUE, -1, false);
+            GetEntry getEntry2 = new GetEntry(key, VALUE, -1, false);
+
+            // OK, those threads should be blocked waiting for the new cache
+            // entry to appear. Sleep for a bit to simulate the time taken to
+            // build the cache entry
+            PutInCache putInCache = new PutInCache(key, VALUE, 500); 
+            
+            // pass that instance to the MTTR
+            TestRunnable[] trs = {getEntry1, getEntry2, putInCache};
+            MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs);
+
+            // kickstarts the MTTR & fires off threads
+            try {
+                mttr.runTestRunnables(5000);
+            } catch (Throwable e) {
+                fail("Thread should have blocked until a new cache entry was ready");
+            }
+        }
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * new cache entry correctly
+     */
+    public void testNewEntryCancel() {
+        final String key = "newCancel";
+        final String NEW_VALUE = VALUE + "...";
+
+        try {
+            admin.getFromCache(key, -1);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry
+            // We can't use GrobeUtils, because joining functionality is missing
+            GetEntrySimple getEntry = new GetEntrySimple(key, NEW_VALUE, CacheEntry.INDEFINITE_EXPIRY, true);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // The above thread will be blocked waiting for the new content
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+            }
+
+            // Now cancel the update (eg because an exception occurred while building the content).
+            // This will unblock the other thread and it will receive a NeedsRefreshException.
+            admin.cancelUpdate(key);
+
+            // Wait a bit for the other thread to update the cache
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+            }
+
+            try {
+                Object newValue = admin.getFromCache(key, CacheEntry.INDEFINITE_EXPIRY);
+                assertEquals(NEW_VALUE, newValue);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                e.printStackTrace();
+                fail("A NeedsRefreshException should not have been thrown. content=" + e.getCacheContent() + ", "+e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Verify that we can concurrently access the cache without problems
+     */
+    public void testPut() {
+        Thread[] thread = new Thread[THREAD_COUNT];
+
+        for (int idx = 0; idx < THREAD_COUNT; idx++) {
+            OSGeneralTest runner = new OSGeneralTest();
+            thread[idx] = new Thread(runner);
+            thread[idx].start();
+        }
+
+        boolean stillAlive;
+
+        do {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+
+            stillAlive = false;
+
+            int i = 0;
+
+            while ((i < thread.length) && !stillAlive) {
+                stillAlive |= thread[i++].isAlive();
+            }
+        } while (stillAlive);
+    }
+
+    /**
+     * Check that the cache handles simultaneous attempts to access a
+     * stale cache entry correctly
+     */
+    public void testStaleEntry() {
+        String key = "stale";
+        assertFalse("The cache should not be in blocking mode for this test.", admin.isBlocking());
+
+        admin.putInCache(key, VALUE);
+
+        try {
+            // This should throw a NeedsRefreshException since the refresh
+            // period is 0
+            admin.getFromCache(key, 0);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry.
+            // Since blocking mode is currently disabled we should
+            // immediately get back the stale entry
+            GetEntry getEntry = new GetEntry(key, VALUE, 0, false);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // Sleep for a bit to simulate the time taken to build the cache entry
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+            }
+
+            // Putting the entry in the cache should mean that threads now retrieve
+            // the updated entry
+            String newValue = "New value";
+            admin.putInCache(key, newValue);
+
+            getEntry = new GetEntry(key, newValue, -1, false);
+            thread = new Thread(getEntry);
+            thread.start();
+
+            try {
+                Object fromCache = admin.getFromCache(key, -1);
+                assertEquals(newValue, fromCache);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                fail("Should not have received a NeedsRefreshException");
+            }
+
+            // Give the GetEntry thread a chance to finish
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+            }
+        }
+    }
+
+    /**
+     * A test for the updating of a stale entry when CACHE.BLOCKING = TRUE
+     */
+    public void testStaleEntryBlocking() {
+        // A test for the case where oscache.blocking = true
+        admin.destroy();
+
+        Properties p = new Properties();
+        p.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "true");
+        admin = new GeneralCacheAdministrator(p);
+
+        assertTrue("The cache should be in blocking mode for this test.", admin.isBlocking());
+
+        // Use a unique key in case these test entries are being persisted
+        String key = "blocking";
+        String NEW_VALUE = VALUE + " abc";
+        admin.putInCache(key, VALUE);
+
+        try {
+            // Force a NeedsRefreshException
+            admin.getFromCache(key, 0);
+            fail("NeedsRefreshException should have been thrown");
+        } catch (NeedsRefreshException nre) {
+            // Fire off another thread to get the same cache entry.
+            // Since blocking mode is enabled this thread should block
+            // until the entry has been updated.
+            GetEntry getEntry = new GetEntry(key, NEW_VALUE, 0, false);
+            Thread thread = new Thread(getEntry);
+            thread.start();
+
+            // Sleep for a bit to simulate the time taken to build the cache entry
+            try {
+                Thread.sleep(20);
+            } catch (InterruptedException ie) {
+            }
+
+            // Putting the entry in the cache should mean that threads now retrieve
+            // the updated entry
+            admin.putInCache(key, NEW_VALUE);
+
+            getEntry = new GetEntry(key, NEW_VALUE, -1, false);
+            thread = new Thread(getEntry);
+            thread.start();
+
+            try {
+                Object fromCache = admin.getFromCache(key, -1);
+                assertEquals(NEW_VALUE, fromCache);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(key);
+                fail("Should not have received a NeedsRefreshException");
+            }
+        }
+    }
+
+    private static final int RETRY_BY_THREADS = 100000;
+    private static final int NB_THREADS = 4;
+
+    /**
+     * Checks whether the cache handles simultaneous attempts to access a
+     * stable cache entry correctly when the blocking mode is enabled.
+     *
+     * Basically N threads are concurrently trying to access a same stale cache entry and each is cancelling its update. Each thread repeat this operation M times.
+     * The test is sucessfull if after some time, all threads are properly released
+     */
+    public void testConcurrentStaleGets() {
+        GeneralCacheAdministrator staticAdmin = admin;
+        //admin = new GeneralCacheAdministrator(); //avoid poluting other test cases
+
+        try {
+            // A test for the case where oscache.blocking = true
+            //admin.destroy();
+            Properties p = new Properties();
+            p.setProperty(AbstractCacheAdministrator.CACHE_BLOCKING_KEY, "true");
+            admin = new GeneralCacheAdministrator(p);
+
+            assertTrue("The cache should be in blocking mode for this test.", admin.isBlocking());
+
+            String key = "new";
+
+            //First put a value
+            admin.putInCache(key, VALUE);
+
+            try {
+                //Then test without concurrency that it is reported as stale when time-to-live is zero 
+                admin.getFromCache(key, 0);
+                fail("NeedsRefreshException should have been thrown");
+            } catch (NeedsRefreshException nre) {
+                //Ok this is was is excpected, we can release the update
+                admin.cancelUpdate(key);
+            }
+
+            //Then ask N threads to concurrently try to access this stale resource and each should receive a NeedsRefreshException, and cancel the update
+            TestRunnable[] spawnedThreads = new TestRunnable[NB_THREADS];
+
+            for (int threadIndex = 0; threadIndex < NB_THREADS; threadIndex++) {
+                spawnedThreads[threadIndex] = new GetStaleEntryAndCancelUpdate(key, 0, RETRY_BY_THREADS);
+            }
+            MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(spawnedThreads);
+            
+            //kickstarts the MTTR & fires off threads
+            try {
+                mttr.runTestRunnables(120 * 1000);
+            } catch (Throwable e) {
+                fail("at least one thread did not complete");
+                e.printStackTrace();
+            }
+            
+        } finally {
+            // avoid poluting other test cases
+            admin = staticAdmin;
+        }
+    }
+
+    private class GetEntry extends TestRunnable {
+        String key;
+        String value;
+        boolean expectNRE;
+        int time;
+
+        GetEntry(String key, String value, int time, boolean expectNRE) {
+            this.key = key;
+            this.value = value;
+            this.time = time;
+            this.expectNRE = expectNRE;
+        }
+
+        public void runTest() {
+            try {
+                // Get from the cache
+                Object fromCache = admin.getFromCache(key, time);
+                assertEquals(value, fromCache);
+            } catch (NeedsRefreshException nre) {
+                if (!expectNRE) {
+                    admin.cancelUpdate(key);
+                    fail("Thread should have blocked until a new cache entry was ready");
+                } else {
+                    // Put a new piece of content into the cache
+                    admin.putInCache(key, value);
+                }
+            }
+        }
+    }
+    
+    private class GetEntrySimple extends GetEntry {
+        GetEntrySimple(String key, String value, int time, boolean expectNRE) {
+            super(key, value, time, expectNRE);
+        }
+        
+        public void run() {
+            runTest();
+        }
+
+    }
+
+    private class PutInCache extends TestRunnable {
+
+        String key;
+        String value;
+        long wait;
+
+        PutInCache(String key, String value, long wait) {
+            this.key = key;
+            this.value = value;
+            this.wait = wait;
+        }
+
+        public void runTest() {
+            try {
+                Thread.sleep(wait);
+            } catch (InterruptedException ie) {
+                fail("PutInCache thread shouldn't be interrupted.");
+            }
+            admin.putInCache(key, value);
+        }
+    }
+
+    /**
+      * Basically requests a stale entry, expects to receive a NeedsRefreshException, and always cancels the update.
+      */
+    private class GetStaleEntryAndCancelUpdate extends TestRunnable {
+        String key;
+        int retries;
+        int time;
+
+        GetStaleEntryAndCancelUpdate(String key, int time, int retries) {
+            this.key = key;
+            this.time = time;
+            this.retries = retries;
+        }
+
+        public void runTest() {
+            for (int retryIndex = 0; retryIndex < retries; retryIndex++) {
+                try {
+                    // Get from the cache
+                    Object fromCache = admin.getFromCache(key, time);
+                    assertNull("Thread index [" + retryIndex + "] expected stale request [" + retryIndex + "] to be received, got [" + fromCache + "]", fromCache);
+                } catch (NeedsRefreshException nre) {
+                    try {
+                        admin.cancelUpdate(key);
+                    } catch (Throwable t) {
+                        log.error("Thread index [" + retryIndex + "]: Unexpectedly caught exception [" + t + "]", t);
+                        fail("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]");
+                    }
+                } catch (Throwable t) {
+                    log.error("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]", t);
+                    fail("Thread index [" + retryIndex + "] : Unexpectedly caught exception [" + t + "]");
+                }
+            }
+        }
+    }
+
+    private class OSGeneralTest extends TestRunnable {
+        public void doit(int i) {
+            int refreshPeriod = 500 /*millis*/;
+            String key = KEY + (i % UNIQUE_KEYS);
+            admin.putInCache(key, VALUE);
+
+            try {
+                // Get from the cache
+                admin.getFromCache(KEY, refreshPeriod);
+            } catch (NeedsRefreshException nre) {
+                // Get the value
+                // Store in the cache
+                admin.putInCache(KEY, VALUE);
+            }
+
+            // Flush occasionally
+            if ((i % (UNIQUE_KEYS + 1)) == 0) {
+                admin.getCache().flushEntry(key);
+            }
+        }
+
+        public void runTest() {
+            int start = (int) (Math.random() * UNIQUE_KEYS);
+            System.out.print(start + " ");
+
+            for (int i = start; i < (start + ITERATION_COUNT); i++) {
+                doit(i);
+            }
+        }
+    }
+    
+    
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestAbstractCache.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestAbstractCache.java
new file mode 100644
index 0000000..10f05c5
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestAbstractCache.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.CachePersistenceException;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for the AbstractCache class. It tests all public methods of
+ * the AbstractCache and assert the results. It is design to run under JUnit.
+ *
+ * $Id: TestAbstractCache.java 425 2007-03-18 09:45:03Z larst $
+ * @version        $Revision: 425 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public abstract class TestAbstractCache extends TestCase {
+    /**
+     * Invalid cache capacity
+     */
+    protected final int INVALID_MAX_ENTRIES = 0;
+
+    /**
+     * Cache capacity
+     */
+    protected final int MAX_ENTRIES = 3;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    protected TestAbstractCache(String str) {
+        super(str);
+    }
+
+    /**
+     * Test the method that verify if the cache contains a specific key
+     */
+    public abstract void testContainsKey();
+
+    /**
+     * Test the get from the cache
+     */
+    public abstract void testGet();
+
+    /**
+     * Test the capacity setting
+     */
+    public void testGetSetMaxEntries() {
+        getCache().setMaxEntries(MAX_ENTRIES);
+        assertEquals(MAX_ENTRIES, getCache().getMaxEntries());
+
+        // Specify an invalid capacity
+        try {
+            getCache().setMaxEntries(INVALID_MAX_ENTRIES);
+            fail("Cache capacity set with an invalid argument");
+        } catch (Exception e) {
+            // This is what we expect
+        }
+    }
+
+    /**
+     * Test the setting of the memory cache
+     */
+    public void testGetSetMemoryCache() {
+        getCache().setMemoryCaching(true);
+        assertTrue(getCache().isMemoryCaching());
+    }
+
+    /**
+     * Test the iterator retrieval
+     */
+    public abstract void testIterator();
+
+    /**
+     * Test the put into the cache
+     */
+    public abstract void testPut();
+
+    /**
+     * Test the remove from the cache
+     */
+    public abstract void testRemove();
+
+    /**
+     * Test the specific details about the cache algorithm
+     */
+    public abstract void testRemoveItem();
+
+    /**
+     * Test the PersistenceListener setter. Since the persistance listener is
+     * an interface, just call the setter with null
+     */
+    public void testSetPersistenceListener() {
+        getCache().setPersistenceListener(null);
+    }
+
+    // Abstract method that returns an instance of an admin
+    protected abstract AbstractConcurrentReadCache getCache();
+
+    /**
+     * Test that groups are correctly updated on puts and removes
+     * See CACHE-188 and maybe CACHE-244
+     */
+    public void testGroups() {
+      String KEY = "testkey";
+      String KEY2 = "testkey2";
+      String GROUP_NAME = "group1";
+      CacheEntry entry = new CacheEntry(KEY, null);
+      entry.setContent("testvalue");
+      entry.setGroups(new String[] {GROUP_NAME});
+      getCache().put(KEY, entry);
+
+      Map m = getCache().getGroupsForReading();
+      assertNotNull("group must exist", m.get(GROUP_NAME));
+      try {
+        Set group = (Set)m.get(GROUP_NAME);
+        assertEquals(1, group.size());
+        Object keyFromGroup = group.iterator().next();
+        assertEquals(KEY, keyFromGroup);
+      } catch (ClassCastException e) {
+        fail("group should have been a java.util.Set but is a " +
+            m.get(GROUP_NAME).getClass().getName());
+      }
+
+      assertNotNull(getCache().remove(KEY));
+
+      m = getCache().getGroupsForReading();
+      assertNull("group should have been deleted (see CACHE-188)", m.get(GROUP_NAME));
+      getCache().clear();
+
+      // Test if persistence options are correctly considered for groups
+      try {
+        PersistenceListener listener = new MockPersistenceListener();
+        getCache().setPersistenceListener(listener);
+        getCache().setOverflowPersistence(false);
+        getCache().put(KEY, entry);
+        assertTrue(listener.isStored(KEY));
+        Set group = listener.retrieveGroup(GROUP_NAME);
+        assertNotNull(group);
+        assertTrue(group.contains(KEY));
+
+        getCache().remove(KEY);
+        assertFalse(listener.isStored(KEY));
+        getCache().clear();
+
+        // test overflow persistence
+        getCache().setOverflowPersistence(true);
+        getCache().setMaxEntries(1);
+        getCache().put(KEY, entry);
+        assertFalse(listener.isStored(KEY));
+        // is it correct that the group is persisted, even when we use overflow only?
+        // assertFalse(listener.isGroupStored(GROUP_NAME));
+
+        CacheEntry entry2 = new CacheEntry(KEY2);
+        entry2.setContent("testvalue");
+        entry2.setGroups(new String[] {GROUP_NAME});
+        getCache().put(KEY2, entry2);
+        // oldest must have been persisted to disk:
+        assertTrue(listener.isStored(KEY));
+        assertFalse(listener.isStored(KEY2));
+        assertNotNull(getCache().get(KEY2));
+      } catch (CachePersistenceException e) {
+        e.printStackTrace();
+        fail("Excpetion was thrown");
+      }
+    }
+    
+    public void testMisc() {
+        getCache().clear();
+        assertTrue(getCache().capacity() > 0);
+
+        final String KEY = "testkeymisc";
+        final String CONTENT = "testkeymisc";
+
+        CacheEntry entry = new CacheEntry(KEY, null);
+        entry.setContent(CONTENT);
+        
+        if (getCache().contains(entry) == false) {
+            getCache().put(KEY, entry);
+        }
+        assertTrue(getCache().contains(entry));
+        
+        CacheEntry entry2 = new CacheEntry(KEY+"2", null);
+        entry.setContent(CONTENT+"2");
+        getCache().put(entry2.getKey(), entry2);
+
+        Enumeration enumeration = getCache().elements();
+        assertTrue(enumeration.hasMoreElements());
+        while (enumeration.hasMoreElements()) enumeration.nextElement();
+    }
+
+
+    private static class MockPersistenceListener implements PersistenceListener {
+      
+      private Map entries = new HashMap();
+      private Map groups = new HashMap();
+
+      public void clear() throws CachePersistenceException {
+        entries.clear();
+        groups.clear();
+      }
+
+      public PersistenceListener configure(Config config) {
+        return this;
+      }
+
+      public boolean isGroupStored(String groupName) throws CachePersistenceException {
+        return groups.containsKey(groupName);
+      }
+
+      public boolean isStored(String key) throws CachePersistenceException {
+        return entries.containsKey(key);
+      }
+
+      public void remove(String key) throws CachePersistenceException {
+        entries.remove(key);
+      }
+
+      public void removeGroup(String groupName) throws CachePersistenceException {
+        groups.remove(groupName);
+      }
+
+      public Object retrieve(String key) throws CachePersistenceException {
+        return entries.get(key);
+      }
+
+      public Set retrieveGroup(String groupName) throws CachePersistenceException {
+        return (Set)groups.get(groupName);
+      }
+
+      public void store(String key, Object obj) throws CachePersistenceException {
+        entries.put(key, obj);
+      }
+
+      public void storeGroup(String groupName, Set group) throws CachePersistenceException {
+        groups.put(groupName, group);
+      }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestCompleteAlgorithm.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestCompleteAlgorithm.java
new file mode 100644
index 0000000..f87b0f0
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestCompleteAlgorithm.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.base.algorithm package.
+ * It invokes all the test suites of all the other classes of the package,
+ * except abstract ones because they are tested via final ones.
+ *
+ * $Id: TestCompleteAlgorithm.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteAlgorithm extends TestCase {
+    /**
+     * Constructor for the oscache project main test program
+     */
+    public TestCompleteAlgorithm(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteAlgorithm.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all base algorithm cache modules");
+        suite.addTest(TestFIFOCache.suite());
+        suite.addTest(TestLRUCache.suite());
+        suite.addTest(TestUnlimitedCache.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestFIFOCache.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestFIFOCache.java
new file mode 100644
index 0000000..4d107df
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestFIFOCache.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the FIFOCache class. It tests that the algorithm reacts as
+ * expected when entries are removed
+ *
+ * $Id: TestFIFOCache.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestFIFOCache extends TestQueueCache {
+    /**
+     * FIFO Cache object
+     */
+    private static FIFOCache cache = null;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestFIFOCache(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestFIFOCache.class);
+    }
+
+    /**
+     * Abstract method used by the TestAbstractCache class
+     * <p>
+     * @return  A cache instance
+     */
+    public AbstractConcurrentReadCache getCache() {
+        return cache;
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // Create a cache instance on first invocation
+        if (cache == null) {
+            cache = new FIFOCache();
+            assertNotNull(cache);
+        }
+    }
+
+    /**
+     * Test the cache algorithm
+     */
+    public void testRemoveItem() {
+        // Add 2 elements in the cache and ensure that the one to remove is the first
+        // inserted
+        cache.itemPut(KEY);
+        cache.itemPut(KEY + 1);
+        assertTrue(KEY.equals(cache.removeItem()));
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestLRUCache.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestLRUCache.java
new file mode 100644
index 0000000..cff328a
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestLRUCache.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the LRUCache class. It only tests that the algorithm reacts as
+ * expected when entries are removed. All the other tests related to the LRU
+ * algorithm are in the TestNonQueueCache class, since those tests are shared
+ * with the TestUnlimitedCache class.
+ *
+ * $Id: TestLRUCache.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestLRUCache extends TestQueueCache {
+    /**
+     * LRU Cache object
+     */
+    private static LRUCache cache = null;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestLRUCache(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestLRUCache.class);
+    }
+
+    /**
+     * Abstract method used by the TestAbstractCache class
+     * <p>
+     * @return  A cache instance
+     */
+    public AbstractConcurrentReadCache getCache() {
+        return cache;
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // Create a cache instance on first invocation
+        if (cache == null) {
+            cache = new LRUCache();
+            assertNotNull(cache);
+        }
+    }
+
+    /**
+     * Test the cache algorithm
+     */
+    public void testRemoveItem() {
+        // Add 3 elements
+        cache.itemPut(KEY);
+        cache.itemPut(KEY + 1);
+        cache.itemPut(KEY + 2);
+
+        // Get the last element
+        cache.itemRetrieved(KEY);
+
+        // The least recently used item is key + 1
+        assertTrue((KEY + 1).equals(cache.removeItem()));
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestQueueCache.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestQueueCache.java
new file mode 100644
index 0000000..2958e01
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestQueueCache.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.PersistenceListener;
+import com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener;
+import com.opensymphony.oscache.plugins.diskpersistence.TestDiskPersistenceListener;
+
+import java.util.Iterator;
+import java.util.Properties;
+
+/**
+ * Test class for the QueueCache class, which is the base class for FIFO
+ * and LIFO algorithm classes. All the public methods of QueueCache are tested
+ * here.
+ *
+ * $Id: TestQueueCache.java 383 2006-09-10 22:00:01Z larst $
+ * @version        $Revision: 383 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public abstract class TestQueueCache extends TestAbstractCache {
+    /**
+     * Entry content
+     */
+    protected final String CONTENT = "Test Queue Cache content";
+
+    /**
+     * Entry key
+     */
+    protected final String KEY = "Test Queue Cache key";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestQueueCache(String str) {
+        super(str);
+    }
+
+    /**
+     * Test the specific algorithms
+     */
+    public abstract void testRemoveItem();
+
+    /**
+     * Test the clear
+     */
+    public void testClear() {
+        getCache().clear();
+        assertEquals(0, getCache().size());
+    }
+
+    /**
+     * Test the ContainsKey method
+     */
+    public void testContainsKey() {
+        getCache().put(KEY, CONTENT);
+        assertTrue(getCache().containsKey(KEY));
+        getCache().clear();
+    }
+
+    /**
+     * Test the get method
+     */
+    public void testGet() {
+        // Add an entry and verify that it is there
+        getCache().put(KEY, CONTENT);
+        assertTrue(getCache().get(KEY).equals(CONTENT));
+
+        // Call with invalid parameters
+        try {
+            getCache().get(null);
+            fail("Get called with null parameters!");
+        } catch (Exception e) { /* This is what we expect */
+        }
+
+        getCache().clear();
+    }
+
+    /**
+     * Test the getter and setter for the max entries
+     */
+    public void testGetSetMaxEntries() {
+        // Check that the cache is full, then chop it by one and assert that
+        // an element has been removed
+        for (int count = 0; count < MAX_ENTRIES; count++) {
+            getCache().put(KEY + count, CONTENT + count);
+        }
+
+        assertEquals(MAX_ENTRIES, getCache().size());
+        getCache().setMaxEntries(MAX_ENTRIES - 1);
+        assertEquals(MAX_ENTRIES - 1, getCache().getMaxEntries());
+        assertEquals(MAX_ENTRIES - 1, getCache().size());
+
+        // Specify an invalid capacity
+        try {
+            getCache().setMaxEntries(INVALID_MAX_ENTRIES);
+            fail("Cache capacity set with an invalid argument");
+        } catch (Exception e) {
+            // This is what we expect
+        }
+
+        getCache().clear();
+    }
+
+    /**
+     * Test the iterator
+     */
+    public void testIterator() {
+        // Verify that the iterator returns MAX_ENTRIES and no more elements
+        int nbEntries = getCache().size();
+        Iterator iterator = getCache().entrySet().iterator();
+        assertNotNull(iterator);
+
+        for (int count = 0; count < nbEntries; count++) {
+            assertNotNull(iterator.next());
+        }
+
+        assertTrue(!iterator.hasNext());
+    }
+
+    /**
+     * Test the put method
+     */
+    public void testPut() {
+        // Put elements in cache
+        for (int count = 0; count < MAX_ENTRIES; count++) {
+            getCache().put(KEY + count, CONTENT + count);
+        }
+
+        // Call with invalid parameters
+        try {
+            getCache().put(null, null);
+            fail("Put called with null parameters!");
+        } catch (Exception e) { /* This is what we expect */
+        }
+
+        getCache().clear();
+    }
+
+    /**
+     * Test the put method with overflow parameter set
+     */
+    public void testPutOverflow() {
+        // Create a listener
+        PersistenceListener listener = new DiskPersistenceListener();
+
+        Properties p = new Properties();
+        p.setProperty("cache.path", TestDiskPersistenceListener.CACHEDIR);
+        p.setProperty("cache.memory", "true");
+        p.setProperty("cache.persistence.overflow.only", "true");
+        p.setProperty("cache.persistence.class", "com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener");
+        listener.configure(new Config(p));
+        getCache().setPersistenceListener(listener);
+        getCache().clear();
+        getCache().setMaxEntries(MAX_ENTRIES);
+        getCache().setOverflowPersistence(true);
+
+        if (getCache() instanceof UnlimitedCache) {
+            return; // nothing to test since memory will never overflow.
+        }
+
+        // Put elements in cache
+        for (int count = 0; count <= MAX_ENTRIES; count++) {
+            getCache().put(KEY + count, CONTENT + count);
+        }
+
+        try {
+            int numPersisted = 0;
+
+            // Check that number of elements persisted == 1 if it is an overflow cache or all
+            // if it is not overflow and writes every time.
+            for (int count = 0; count <= MAX_ENTRIES; count++) {
+                if (getCache().getPersistenceListener().isStored(KEY + count)) {
+                    numPersisted++;
+                }
+            }
+
+            if (getCache().isOverflowPersistence()) {
+                assertTrue("Only 1 element should have been persisted ", numPersisted == 1);
+            } else {
+                assertTrue("All elements should have been persisted ", numPersisted == (MAX_ENTRIES + 1));
+            }
+        } catch (Exception e) {
+            fail();
+        }
+
+        getCache().clear();
+    }
+  
+    /**
+     * Test if bug CACHE-255 disappeared.
+     */
+    public void testBugCache255() {
+        if (!getCache().isMemoryCaching()) {
+            return; // nothing to test since memory won't be used.
+        }
+        if (getCache() instanceof UnlimitedCache) {
+            return; // nothing to test since memory will never overflow.
+        }
+
+        // fill up the cache
+        for (int count = 0; count < MAX_ENTRIES; count++) {
+            getCache().put(KEY + count, CONTENT + count);
+        }
+
+        // get the old value
+        Object oldValue = getCache().put(KEY + MAX_ENTRIES, CONTENT + MAX_ENTRIES);
+
+        assertEquals("Evicted object content should be the same", CONTENT + "0", oldValue);
+
+        getCache().clear();
+    }
+
+    /**
+     * Test the remove from cache
+     */
+    public void testRemove() {
+        getCache().put(KEY, CONTENT);
+
+        // Remove the object and assert the return
+        assertNotNull(getCache().remove(KEY));
+        getCache().clear();
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/algorithm/TestUnlimitedCache.java b/src/test/java/com/opensymphony/oscache/base/algorithm/TestUnlimitedCache.java
new file mode 100644
index 0000000..7f53f17
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/algorithm/TestUnlimitedCache.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.algorithm;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the Unlimited cache algorithm. Most of the tests are done
+ * in the TestNonQueueCache class, so only algorithm specific tests are done
+ * here. Since this is an unlimited cache, there's not much to test about
+ * the algorithm.
+ *
+ * $Id: TestUnlimitedCache.java 365 2006-06-15 16:27:17Z ltorunski $
+ * @version        $Revision: 365 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestUnlimitedCache extends TestQueueCache {
+    /**
+     * Unlimited Cache object
+     */
+    private static UnlimitedCache cache = null;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestUnlimitedCache(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestUnlimitedCache.class);
+    }
+
+    /**
+     * Abstract method used by the TestAbstractCache class
+     * <p>
+     * @return  A cache instance
+     */
+    public AbstractConcurrentReadCache getCache() {
+        return cache;
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // Create a cache instance on first invocation
+        if (cache == null) {
+            cache = new UnlimitedCache();
+            assertNotNull(cache);
+        }
+    }
+
+    /**
+     * Test the getter and setter for the max entries. It overrides the TestQueueCache
+     * one since it shouldn't have any effect in unlimited cache
+     */
+    public void testGetSetMaxEntries() {
+        // Check that the max entries cannot be changed
+        int entryCount = getCache().getMaxEntries();
+        getCache().setMaxEntries(entryCount - 1);
+        assertEquals(entryCount, getCache().getMaxEntries());
+    }
+
+    /**
+     * Test the cache algorithm
+     */
+    public void testRemoveItem() {
+        // Add an item, and ensure that it is not removable
+        cache.itemPut(KEY);
+        assertNull(cache.removeItem());
+    }
+    
+    /**
+     * Test that groups are correctly updated on puts and removes
+     */
+    public void testGroups() {
+    	// test not possible, because can't reduce cache max entries for this test	
+    }
+
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCacheEntryEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestCacheEntryEvent.java
new file mode 100644
index 0000000..726d5a8
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCacheEntryEvent.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This is the test class for the CacheEntryEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestCacheEntryEvent.java 419 2007-03-17 13:01:19Z larst $
+ * @version        $Revision: 419 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCacheEntryEvent extends TestCase {
+    /**
+     * Constants required for the test
+     */
+    private final String KEY = "Test cache entry event key";
+    private final String KEY_2 = "Test cache entry event key 2";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheEntryEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheEntryEvent.class);
+    }
+
+    /**
+     * Test the CacheEntryEvent class
+     */
+    public void testCacheEntryEvent() {
+        // Create all the required objects
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+        Cache map = new Cache(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+
+        // test with key
+        CacheEntry entry = new CacheEntry(KEY);
+        CacheEntryEvent event = new CacheEntryEvent(map, entry, null);
+
+        // Get back the values and assert them
+        assertEquals(event.getEntry(), entry);
+        assertEquals(event.getKey(), KEY);
+        assertEquals(event.getMap(), map);
+        assertNull(event.getOrigin());
+        
+        CacheEntry entry2 = new CacheEntry(KEY_2);
+        CacheEntryEvent event2 = new CacheEntryEvent(map, entry2);
+
+        // Get back the values and assert them
+        assertEquals(event2.getEntry(), entry2);
+        assertEquals(event2.getKey(), KEY_2);
+        assertEquals(event2.getMap(), map);
+        assertNull(event2.getOrigin());
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCacheGroupEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestCacheGroupEvent.java
new file mode 100644
index 0000000..9a4c4e3
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCacheGroupEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This is the test class for the CacheGroupEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestCacheEntryEvent.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author Lars Torunski
+ */
+public final class TestCacheGroupEvent extends TestCase {
+
+    /**
+     * Constants required for the test
+     */
+    private final String TEST_GROUP = "testGroup";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheGroupEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheGroupEvent.class);
+    }
+
+    /**
+     * Test the CacheEntryEvent class
+     */
+    public void testCacheEntryEvent() {
+        // Create all the required objects
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+        Cache map = new Cache(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+
+        // three parameters
+        CacheGroupEvent event = new CacheGroupEvent(map, TEST_GROUP, null);
+
+        // Get back the values and assert them
+        assertEquals(event.getMap(), map);
+        assertEquals(event.getGroup(), TEST_GROUP);
+        assertNull(event.getOrigin());
+        
+        // two parameters
+        CachePatternEvent event2 = new CachePatternEvent(map, TEST_GROUP);
+
+        // Get back the values and assert them
+        assertEquals(event2.getMap(), map);
+        assertEquals(event.getGroup(), TEST_GROUP);
+        assertNull(event2.getOrigin());
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCacheMapAccessEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestCacheMapAccessEvent.java
new file mode 100644
index 0000000..70023d0
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCacheMapAccessEvent.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.CacheEntry;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This is the test class for the CacheMapAccessEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestCacheMapAccessEvent.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCacheMapAccessEvent extends TestCase {
+    private final String KEY = "Test cache map access event key";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheMapAccessEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheMapAccessEvent.class);
+    }
+
+    /**
+     * Test the CacheMapAccessEvent class
+     */
+    public void testCacheMapAccessEvent() {
+        // Create an object and check the parameters
+        CacheEntry entry = new CacheEntry(KEY);
+        CacheMapAccessEvent event = new CacheMapAccessEvent(CacheMapAccessEventType.HIT, entry);
+        assertEquals(event.getCacheEntry(), entry);
+        assertEquals(event.getCacheEntryKey(), KEY);
+        assertEquals(event.getEventType(), CacheMapAccessEventType.HIT);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCachePatternEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestCachePatternEvent.java
new file mode 100644
index 0000000..11e0251
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCachePatternEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This is the test class for the CachePatternEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestCacheEntryEvent.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author Lars Torunski
+ */
+public final class TestCachePatternEvent extends TestCase {
+
+    /**
+     * Constants required for the test
+     */
+    private final String TEST_PATTERN = "testPattern";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCachePatternEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCachePatternEvent.class);
+    }
+
+    /**
+     * Test the CacheEntryEvent class
+     */
+    public void testCacheEntryEvent() {
+        // Create all the required objects
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+        Cache map = new Cache(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+
+        // three parameters
+        CachePatternEvent event = new CachePatternEvent(map, TEST_PATTERN, null);
+
+        // Get back the values and assert them
+        assertEquals(event.getMap(), map);
+        assertEquals(event.getPattern(), TEST_PATTERN);
+        assertNull(event.getOrigin());
+        
+        // two parameters
+        CachePatternEvent event2 = new CachePatternEvent(map, TEST_PATTERN);
+
+        // Get back the values and assert them
+        assertEquals(event2.getMap(), map);
+        assertEquals(event.getPattern(), TEST_PATTERN);
+        assertNull(event2.getOrigin());
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCachewideEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestCachewideEvent.java
new file mode 100644
index 0000000..76fd5fd
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCachewideEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import java.util.Date;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This is the test class for the CachewideEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestCacheEntryEvent.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author Lars Torunski
+ */
+public final class TestCachewideEvent extends TestCase {
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCachewideEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCachewideEvent.class);
+    }
+
+    /**
+     * Test the CacheEntryEvent class
+     */
+    public void testCacheEntryEvent() {
+        // Create all the required objects
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+
+        Date date = new Date();
+        CachewideEvent event = new CachewideEvent(admin.getCache(), date, null);
+                
+        // Get back the values and assert them
+        assertEquals(event.getDate(), date);
+        assertEquals(event.getCache(), admin.getCache());
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestCompleteEvents.java b/src/test/java/com/opensymphony/oscache/base/events/TestCompleteEvents.java
new file mode 100644
index 0000000..8dbb029
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestCompleteEvents.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.base.events package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteEvents.java 419 2007-03-17 13:01:19Z larst $
+ * @version        $Revision: 419 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteEvents extends TestCase {
+    /**
+     * Constructor for the oscache module main test program
+     */
+    public TestCompleteEvents(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteEvents.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all base cache modules");
+        suite.addTest(TestCacheEntryEvent.suite());
+        suite.addTest(TestCacheMapAccessEvent.suite());
+        suite.addTest(TestScopeEvent.suite());
+        suite.addTest(TestCachewideEvent.suite());
+        suite.addTest(TestCachePatternEvent.suite());
+        suite.addTest(TestCacheGroupEvent.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/base/events/TestScopeEvent.java b/src/test/java/com/opensymphony/oscache/base/events/TestScopeEvent.java
new file mode 100644
index 0000000..4e44d4b
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/base/events/TestScopeEvent.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.base.events;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.util.Date;
+
+/**
+ * This is the test class for the ScopeEvent class. It checks that the
+ * public methods are working properly
+ *
+ * $Id: TestScopeEvent.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestScopeEvent extends TestCase {
+    private final int SCOPE = 3;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestScopeEvent(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestScopeEvent.class);
+    }
+
+    /**
+     * Test the ScopeEvent class
+     */
+    public void testScopeEvent() {
+        Date date = new Date();
+
+        // Create an object and check the parameters
+        ScopeEvent event = new ScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, SCOPE, date, null);
+        assertEquals(event.getEventType(), ScopeEventType.ALL_SCOPES_FLUSHED);
+        assertEquals(event.getScope(), SCOPE);
+        assertTrue(event.getDate().equals(date));
+
+        event = new ScopeEvent(ScopeEventType.SCOPE_FLUSHED, SCOPE, date, null);
+        assertEquals(event.getEventType(), ScopeEventType.SCOPE_FLUSHED);
+        assertEquals(event.getScope(), SCOPE);
+        assertTrue(event.getDate().equals(date));
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/extra/TestCacheEntryEventListenerImpl.java b/src/test/java/com/opensymphony/oscache/extra/TestCacheEntryEventListenerImpl.java
new file mode 100644
index 0000000..31ec3fb
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/extra/TestCacheEntryEventListenerImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import java.util.Date;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.events.CacheEntryEvent;
+import com.opensymphony.oscache.base.events.CacheGroupEvent;
+import com.opensymphony.oscache.base.events.CachePatternEvent;
+import com.opensymphony.oscache.base.events.CachewideEvent;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test the cache entry event listener implementation
+ *
+ * $Id: TestCacheEntryEventListenerImpl.java 418 2007-03-17 12:18:51Z larst $
+ * @version        $Revision: 418 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestCacheEntryEventListenerImpl extends TestCase {
+    /**
+     * Key used for this test
+     */
+    private final String KEY = "Test Cache Entry Event Listener Impl Key";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheEntryEventListenerImpl(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheEntryEventListenerImpl.class);
+    }
+
+    /**
+     * Test the basic implementation
+     */
+    public void testCacheEntryEventListenerImpl() {
+        // Construct the objects required for the tests
+        CacheEntry entry = new CacheEntry(KEY);
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+        Cache cache = new Cache(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+        CacheEntryEvent event = new CacheEntryEvent(cache, entry, null);
+        CacheEntryEventListenerImpl listener = new CacheEntryEventListenerImpl();
+
+        // Assert the counters
+        assertEquals(listener.getEntryAddedCount(), 0);
+        assertEquals(listener.getEntryFlushedCount(), 0);
+        assertEquals(listener.getEntryRemovedCount(), 0);
+        assertEquals(listener.getEntryUpdatedCount(), 0);
+        assertEquals(listener.getGroupFlushedCount(), 0);
+        assertEquals(listener.getPatternFlushedCount(), 0);
+        assertEquals(listener.getCacheFlushedCount(), 0);
+
+        // Generate an event of each type
+        listener.cacheEntryAdded(event);
+        listener.cacheEntryFlushed(event);
+        listener.cacheEntryRemoved(event);
+        listener.cacheEntryUpdated(event);
+
+        listener.cacheFlushed(new CachewideEvent(cache, new Date(), null));
+        listener.cacheGroupFlushed(new CacheGroupEvent(cache, "testGroup", null));
+        listener.cachePatternFlushed(new CachePatternEvent(cache, "testPattern", null));
+
+        // Assert the counters
+        assertEquals(listener.getEntryAddedCount(), 1);
+        assertEquals(listener.getEntryFlushedCount(), 1);
+        assertEquals(listener.getEntryRemovedCount(), 1);
+        assertEquals(listener.getEntryUpdatedCount(), 1);
+        assertEquals(listener.getGroupFlushedCount(), 1);
+        assertEquals(listener.getPatternFlushedCount(), 1);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/extra/TestCacheMapAccessEventListenerImpl.java b/src/test/java/com/opensymphony/oscache/extra/TestCacheMapAccessEventListenerImpl.java
new file mode 100644
index 0000000..3abcdbc
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/extra/TestCacheMapAccessEventListenerImpl.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.events.CacheMapAccessEvent;
+import com.opensymphony.oscache.base.events.CacheMapAccessEventType;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test the cache map access event listener implementation
+ *
+ * $Id: TestCacheMapAccessEventListenerImpl.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestCacheMapAccessEventListenerImpl extends TestCase {
+    /**
+     * Key used for this test
+     */
+    private final String KEY = "Test Cache Map Access Event Listener Impl Key";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestCacheMapAccessEventListenerImpl(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestCacheMapAccessEventListenerImpl.class);
+    }
+
+    /**
+     * Test the basic implementation of the listener
+     */
+    public void testCacheMapAccessEventListenerImpl() {
+        // Build objects required for the tests
+        CacheEntry entry = new CacheEntry(KEY);
+        CacheMapAccessEventListenerImpl listener = new CacheMapAccessEventListenerImpl();
+
+        // Genereate events
+        listener.accessed(new CacheMapAccessEvent(CacheMapAccessEventType.HIT, entry));
+        listener.accessed(new CacheMapAccessEvent(CacheMapAccessEventType.HIT, entry));
+        listener.accessed(new CacheMapAccessEvent(CacheMapAccessEventType.STALE_HIT, entry));
+        listener.accessed(new CacheMapAccessEvent(CacheMapAccessEventType.MISS, entry));
+
+        // Assert the counters
+        assertEquals(listener.getHitCount(), 2);
+        assertEquals(listener.getStaleHitCount(), 1);
+        assertEquals(listener.getMissCount(), 1);
+
+        // Reset the counts
+        listener.reset();
+        assertEquals(listener.getHitCount(), 0);
+        assertEquals(listener.getStaleHitCount(), 0);
+        assertEquals(listener.getMissCount(), 0);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/extra/TestCompleteExtra.java b/src/test/java/com/opensymphony/oscache/extra/TestCompleteExtra.java
new file mode 100644
index 0000000..82be560
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/extra/TestCompleteExtra.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.extra package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteExtra.java 417 2007-03-17 11:42:20Z larst $
+ * @version        $Revision: 417 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteExtra extends TestCase {
+    /**
+     * Constructor for the osCache Cache Extra package main test program
+     */
+    public TestCompleteExtra(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteExtra.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the test suites of all the project classes
+        TestSuite suite = new TestSuite("Test all extra cache modules");
+        suite.addTest(TestCacheEntryEventListenerImpl.suite());
+        suite.addTest(TestCacheMapAccessEventListenerImpl.suite());
+        suite.addTest(TestScopeEventListenerImpl.suite());
+        suite.addTest(TestStatisticListenerImpl.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/extra/TestScopeEventListenerImpl.java b/src/test/java/com/opensymphony/oscache/extra/TestScopeEventListenerImpl.java
new file mode 100644
index 0000000..732cf8c
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/extra/TestScopeEventListenerImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import com.opensymphony.oscache.base.events.ScopeEvent;
+import com.opensymphony.oscache.base.events.ScopeEventType;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.util.Date;
+
+/**
+ * Test the scope event listener implementation
+ *
+ * $Id: TestScopeEventListenerImpl.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestScopeEventListenerImpl extends TestCase {
+    private static final int PAGE_SCOPE = 1;
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestScopeEventListenerImpl(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestScopeEventListenerImpl.class);
+    }
+
+    /**
+     * Test the basic implementation of this listener
+     */
+    public void testScopeEventListenerImpl() {
+        // Construct the object we need for the test
+        ScopeEventListenerImpl listener = new ScopeEventListenerImpl();
+
+        // Generates events
+        listener.scopeFlushed(new ScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, PAGE_SCOPE, new Date()));
+        listener.scopeFlushed(new ScopeEvent(ScopeEventType.SCOPE_FLUSHED, PAGE_SCOPE, new Date()));
+
+        // Assert the counters
+        assertEquals(listener.getApplicationScopeFlushCount(), 1);
+        assertEquals(listener.getPageScopeFlushCount(), 2);
+        assertEquals(listener.getRequestScopeFlushCount(), 1);
+        assertEquals(listener.getSessionScopeFlushCount(), 1);
+        assertEquals(listener.getTotalScopeFlushCount(), 5);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/extra/TestStatisticListenerImpl.java b/src/test/java/com/opensymphony/oscache/extra/TestStatisticListenerImpl.java
new file mode 100644
index 0000000..fa7778e
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/extra/TestStatisticListenerImpl.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.extra;
+
+import java.util.Date;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.events.CacheEntryEvent;
+import com.opensymphony.oscache.base.events.CacheGroupEvent;
+import com.opensymphony.oscache.base.events.CachePatternEvent;
+import com.opensymphony.oscache.base.events.CachewideEvent;
+import com.opensymphony.oscache.base.events.ScopeEvent;
+import com.opensymphony.oscache.base.events.ScopeEventType;
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test the cache entry event listener implementation
+ *
+ * $Id: TestCacheEntryEventListenerImpl.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ */
+public class TestStatisticListenerImpl extends TestCase {
+    
+    private static final int PAGE_SCOPE = 1;
+
+    /**
+     * Key used for this test
+     */
+    private final String KEY = "Test Statistikc Listener Impl Key";
+
+    /**
+     * Constructor
+     * <p>
+     * @param str The test name (required by JUnit)
+     */
+    public TestStatisticListenerImpl(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The name of this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestStatisticListenerImpl.class);
+    }
+
+    /**
+     * Test the basic implementation
+     */
+    public void testCacheEntryEventListenerImpl() {
+        // Construct the objects required for the tests
+        CacheEntry entry = new CacheEntry(KEY);
+        GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
+        Cache cache = new Cache(admin.isMemoryCaching(), admin.isUnlimitedDiskCache(), admin.isOverflowPersistence());
+        CacheEntryEvent event = new CacheEntryEvent(cache, entry, null);
+        StatisticListenerImpl listener = new StatisticListenerImpl();
+
+        // Assert the counters
+        assertEquals(listener.getEntriesAdded(), 0);
+        assertEquals(listener.getFlushCount(), 0);
+        assertEquals(listener.getEntriesRemoved(), 0);
+        assertEquals(listener.getEntriesUpdated(), 0);
+        assertEquals(listener.getHitCount(), 0);
+        assertEquals(listener.getHitCountSum(), 0);
+        assertEquals(listener.getMissCount(), 0);
+        assertEquals(listener.getMissCountSum(), 0);
+        assertEquals(listener.getStaleHitCount(), 0);
+        assertEquals(listener.getStaleHitCountSum(), 0);
+        
+        // Generate an event of each type
+        listener.cacheEntryAdded(event);
+        listener.cacheEntryFlushed(event);
+        listener.cacheEntryRemoved(event);
+        listener.cacheEntryUpdated(event);
+        
+        listener.scopeFlushed(new ScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, PAGE_SCOPE, new Date()));
+        listener.scopeFlushed(new ScopeEvent(ScopeEventType.SCOPE_FLUSHED, PAGE_SCOPE, new Date()));
+
+        listener.cacheFlushed(new CachewideEvent(cache, new Date(), null));
+        listener.cacheGroupFlushed(new CacheGroupEvent(cache, "testGroup"));
+        listener.cachePatternFlushed(new CachePatternEvent(cache, "testPattern"));
+
+        // Assert the counters
+        assertEquals(listener.getEntriesAdded(), 1);
+        assertEquals(listener.getFlushCount(), 6);
+        assertEquals(listener.getEntriesRemoved(), 1);
+        assertEquals(listener.getEntriesUpdated(), 1);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/general/TestCompleteGeneral.java b/src/test/java/com/opensymphony/oscache/general/TestCompleteGeneral.java
new file mode 100644
index 0000000..7a26c27
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/general/TestCompleteGeneral.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.general;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.general package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteGeneral.java 465 2007-05-19 15:30:01Z larst $
+ * @version        $Revision: 465 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteGeneral extends TestCase {
+    /**
+     * Constructor for the osCache Cache project main test program
+     */
+    public TestCompleteGeneral(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteGeneral.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all General cache package");
+        suite.addTest(TestGeneralCacheAdministrator.suite());
+        suite.addTest(TestConcurrent.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/general/TestConcurrent.java b/src/test/java/com/opensymphony/oscache/general/TestConcurrent.java
new file mode 100644
index 0000000..2f332b9
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/general/TestConcurrent.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.general;
+
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.opensymphony.oscache.base.AbstractCacheAdministrator;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Testing concurrent API accesses.
+ * 
+ * @author $Author: larst $
+ * @version $Revision: 385 $
+ */
+public class TestConcurrent extends TestCase {
+    
+    private static transient final Log log = LogFactory.getLog(GeneralCacheAdministrator.class); //TestConcurrency.class
+
+    // Static instance of a cache administrator
+    private GeneralCacheAdministrator admin = null;
+
+    // Constants needed in the tests
+    private final String KEY = "ConcurrentKey";
+    private final String VALUE = "ConcurrentContent";
+    private static final int THREAD_COUNT = 5;
+    private static final int CACHE_SIZE_THREAD = 2000;
+    private static final int CACHE_SIZE = THREAD_COUNT * CACHE_SIZE_THREAD;
+
+    public TestConcurrent(String str) {
+        super(str);
+    }
+
+    /**
+    * This methods returns the name of this test class to JUnit
+    * <p>
+    * @return The name of this class
+    */
+    public static Test suite() {
+        return new TestSuite(TestConcurrent.class);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a new Cache
+        if (admin == null) {
+            Properties config = new Properties();
+            config.setProperty(AbstractCacheAdministrator.CACHE_CAPACITY_KEY, Integer.toString(CACHE_SIZE));
+            admin = new GeneralCacheAdministrator(config);
+            assertNotNull(admin);
+            log.info("Cache Size = " + admin.getCache().getSize());
+        }
+    }
+
+    /**
+    * Tests concurrent accesses.
+    * @see http://jira.opensymphony.com/browse/CACHE-279
+    */
+    public void testConcurrentCreation10000() {
+        Thread[] thread = new Thread[THREAD_COUNT];
+        
+        log.info("Ramping threads...");
+        for (int idx = 0; idx < THREAD_COUNT; idx++) {
+            CreationTest runner = new CreationTest(idx);
+            thread[idx] = new Thread(runner);
+            thread[idx].start();
+        }
+
+        log.info("Waiting....");
+        boolean stillAlive;
+        do {
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+
+            stillAlive = false;
+            for (int i = 0; i < thread.length; i++) {                
+                stillAlive |= thread[i].isAlive();
+            }
+        } while (stillAlive);
+        log.info("All threads finished. Cache Size = " + admin.getCache().getSize());
+        
+        assertTrue("Unexpected amount of objects in the cache: " + admin.getCache().getSize(), CACHE_SIZE == admin.getCache().getSize());
+    }
+ 
+    private class CreationTest implements Runnable {
+        
+        private String prefixKey;
+        
+        public CreationTest(int idx) {
+            prefixKey = KEY + "_" + Integer.toString(idx) + "_";
+            Thread.currentThread().setName("CreationTest-"+idx);
+            log.info(Thread.currentThread().getName() + " is running...");
+        }
+        
+        public void run() {
+            for (int i = 0; i < CACHE_SIZE_THREAD; i++) {
+                String key = prefixKey + Integer.toString(i);
+                try {
+                    // Get from the cache
+                    admin.getFromCache(key);
+                } catch (NeedsRefreshException nre) {
+                    // Get the value
+                    // Store in the cache
+                    admin.putInCache(key, VALUE);
+                }
+            }
+            log.info(Thread.currentThread().getName() + " finished.");
+        }
+    }
+
+}
diff --git a/src/test/java/com/opensymphony/oscache/general/TestGeneralCacheAdministrator.java b/src/test/java/com/opensymphony/oscache/general/TestGeneralCacheAdministrator.java
new file mode 100644
index 0000000..b99fe24
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/general/TestGeneralCacheAdministrator.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.general;
+
+import java.util.Date;
+
+import com.opensymphony.oscache.base.*;
+import com.opensymphony.oscache.extra.CacheEntryEventListenerImpl;
+import com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test all the public methods of the GeneralCacheAdministrator class. Since
+ * this class extends the TestAbstractCacheAdministrator class, the
+ * AbstractCacheAdministrator is tested when invoking this class.
+ *
+ * $Id: TestGeneralCacheAdministrator.java 425 2007-03-18 09:45:03Z larst $
+ * @version        $Revision: 425 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class TestGeneralCacheAdministrator extends TestAbstractCacheAdministrator {
+    // Constants used thru all the tests
+    private static final String KEY = "Test General Cache Admin Key";
+    private static final int NO_REFRESH_NEEDED = CacheEntry.INDEFINITE_EXPIRY;
+    private static final int REFRESH_NEEDED = 0;
+    private static final String CONTENT = "Content for the general cache admin test";
+    private static final String WILL_NOT_FLUSH_PATTERN = "This key won't flush";
+    private static final String GROUP1 = "group1";
+    private static final String GROUP2 = "group2";
+    private static final String GROUP3 = "group3";
+
+    // Constants for listener counters
+    private static final int NB_CACHE_HITS = 7;
+    private static final int NB_CACHE_STALE_HITS = 7;
+    private static final int NB_CACHE_MISSED = 1;
+    private static final int NB_ADD = 7;
+    private static final int NB_UPDATED = 2;
+    private static final int NB_FLUSH = 3;
+    private static final int NB_REMOVED = 0;
+    private static final int NB_GROUP_FLUSH = 2;
+    private static final int NB_PATTERN_FLUSH = 1;
+
+    // Static instance of a cache administrator
+    static GeneralCacheAdministrator admin = null;
+
+    // Declare the listeners
+    private CacheEntryEventListenerImpl cacheEntryEventListener = null;
+    private CacheMapAccessEventListenerImpl cacheMapAccessEventListener = null;
+
+    /**
+     * Class constructor
+     * <p>
+     * @param str Test name (required by JUnit)
+     */
+    public TestGeneralCacheAdministrator(String str) {
+        super(str);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        return new TestSuite(TestGeneralCacheAdministrator.class);
+    }
+
+    /**
+     * Abstract method used by the TestAbstractCacheAdministrator class
+     * <p>
+     * @return  An administrator instance
+     */
+    public AbstractCacheAdministrator getAdmin() {
+        return admin;
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a administrator
+        admin = new GeneralCacheAdministrator();
+        assertNotNull(admin);
+        cacheEntryEventListener = new CacheEntryEventListenerImpl();
+        cacheMapAccessEventListener = new CacheMapAccessEventListenerImpl();
+
+        // Register the listeners on the cache map
+        admin.getCache().addCacheEventListener(cacheEntryEventListener);
+        admin.getCache().addCacheEventListener(cacheMapAccessEventListener);
+    }
+
+    /**
+     * Validate the CacheEntryEventListener's data
+     */
+    public void testCacheEntryEventListenerCounters() {
+        populate();
+        assertEquals(NB_ADD, cacheEntryEventListener.getEntryAddedCount());
+        assertEquals(NB_REMOVED, cacheEntryEventListener.getEntryRemovedCount());
+        assertEquals(NB_UPDATED, cacheEntryEventListener.getEntryUpdatedCount());
+        assertEquals(NB_GROUP_FLUSH, cacheEntryEventListener.getGroupFlushedCount());
+        assertEquals(NB_PATTERN_FLUSH, cacheEntryEventListener.getPatternFlushedCount());
+        assertEquals(NB_FLUSH, cacheEntryEventListener.getEntryFlushedCount());
+    }
+
+    /**
+     * Validate the CacheEntryEventListener's data
+     */
+    public void testCacheMapAccessEventListenerCounters() {
+        populate();
+
+        int missCount = cacheMapAccessEventListener.getMissCount();
+
+        if (NB_CACHE_MISSED != missCount) {
+            fail("We expected " + NB_CACHE_MISSED + " misses but got " + missCount + "." + " This is probably due to existing disk cache, delete it and re-run" + " the test");
+        }
+
+        assertEquals(NB_CACHE_HITS, cacheMapAccessEventListener.getHitCount());
+        assertEquals(NB_CACHE_STALE_HITS, cacheMapAccessEventListener.getStaleHitCount());
+    }
+
+    /**
+     * Ensure that item may be flushed by key pattern
+     */
+    public void testFlushPattern() {
+        // Put some content in cache
+        admin.putInCache(KEY, CONTENT);
+
+        // Call flush pattern with parameters that must NOT flush our object
+        admin.flushPattern(WILL_NOT_FLUSH_PATTERN);
+        admin.flushPattern("");
+        admin.flushPattern(null);
+
+        // Ensure that our object is not gone
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, false));
+
+        // This time we flush it for real
+        admin.flushPattern(KEY.substring(1, 2));
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, true));
+    }
+
+    /**
+     * Ensure that item may be flushed by the entry itself
+     */
+    public void testFlushEntry() {
+        // Put some content in cache
+        admin.putInCache(KEY, CONTENT);
+
+        // Call flush pattern with parameters that must NOT flush our object
+        admin.flushEntry(WILL_NOT_FLUSH_PATTERN);
+
+        // Ensure that our object is not gone
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, false));
+
+        // This time we flush it for real
+        admin.flushEntry(KEY);
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, true));
+    }
+    
+    /**
+     * Ensure that item may be flushed by flush all
+     */
+    public void testFlushAll() {
+        // Put some content in cache
+        admin.putInCache(KEY, CONTENT);
+
+        // Ensure that our object is not gone
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, false));
+
+        // This time we flush it for real
+        admin.flushAll();
+        assertNotNull(checkObj(KEY, NO_REFRESH_NEEDED, true));
+    }
+    
+    /**
+     * Ensure that the cache groupings work correctly
+     */
+    public void testGroups() {
+        // Flush a non-existent group - should be OK and will still fire a GROUP_FLUSHED event
+        admin.flushGroup(GROUP1);
+
+        // Add some items to various group combinations
+        admin.putInCache("1", "item 1"); // No groups
+        admin.putInCache("2", "item 2", new String[] {GROUP1}); // Just group 1
+        admin.putInCache("3", "item 3", new String[] {GROUP2}); // Just group 2
+        admin.putInCache("4", "item 4", new String[] {GROUP1, GROUP2}); // groups 1 & 2
+        admin.putInCache("5", "item 5", new String[] {GROUP1, GROUP2, GROUP3}); // groups 1,2 & 3
+
+        admin.flushGroup(GROUP3); // This should flush item 5 only
+        assertNotNull(checkObj("5", NO_REFRESH_NEEDED, true));
+        assertNotNull(checkObj("4", NO_REFRESH_NEEDED, false));
+
+        admin.flushGroup(GROUP2); // This should flush items 3 and 4
+        assertNotNull(checkObj("1", NO_REFRESH_NEEDED, false));
+        assertNotNull(checkObj("2", NO_REFRESH_NEEDED, false));
+        assertNotNull(checkObj("3", NO_REFRESH_NEEDED, true));
+        assertNotNull(checkObj("4", NO_REFRESH_NEEDED, true));
+
+        admin.flushGroup(GROUP1); // Flushes item 2
+        assertNotNull(checkObj("1", NO_REFRESH_NEEDED, false));
+        assertNotNull(checkObj("2", NO_REFRESH_NEEDED, true));
+
+        // Test if regrouping a cache entry works
+        admin.putInCache("A", "ABC", new String[] {"A"});
+        admin.putInCache("A", "ABC", new String[] {"A", "B"});
+        admin.putInCache("B", "DEF", new String[] {"B"});
+        admin.flushGroup("B");
+        assertNotNull(checkObj("A", NO_REFRESH_NEEDED, true));
+    }
+
+    /**
+     * Test the main cache functionalities, which are storing and retrieving objects
+     * from it
+     */
+    public void testPutInCacheAndGetFromCache() {
+        // Put some item in cache and get it back right away. It should not need
+        // to be refreshed
+        admin.putInCache(KEY, CONTENT);
+
+        String cacheContent = (String) checkObj(KEY, NO_REFRESH_NEEDED, false);
+        assertTrue(CONTENT.equals(cacheContent));
+
+        // Get the item back again and expect a refresh
+        cacheContent = (String) checkObj(KEY, REFRESH_NEEDED, true);
+        assertTrue(CONTENT.equals(cacheContent));
+
+        // Call the put in cache with invalid values
+        invalidPutInCacheArgument(null, null);
+        admin.putInCache(KEY, null); // This will still update the cache - cached items can be null
+
+        // Call the getFromCache with invalid values
+        invalidGetFromCacheArgument(null, 0);
+
+        // Try to retrieve the values
+        assertNull(checkObj(KEY, NO_REFRESH_NEEDED, false));
+
+        // Try to retrieve an item that is not in the cache
+        Object obj = checkObj("Not in cache", NO_REFRESH_NEEDED, true);
+        assertNull(obj);
+    }
+
+    /**
+     * Test the main cache functionalities, which are storing and retrieving objects
+     * from it
+     */
+    public void testPutInCacheAndGetFromCacheWithPolicy() {
+        String key = "policy";
+
+        // We put content in the cache and get it back
+        admin.putInCache(key, CONTENT, new DummyAlwayRefreshEntryPolicy());
+
+        // Should get a refresh
+        try {
+            admin.getFromCache(key, -1);
+            fail("Should have got a refresh.");
+        } catch (NeedsRefreshException nre) {
+            admin.cancelUpdate(key);
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        if (admin != null) {
+            admin.getCache().removeCacheEventListener(cacheEntryEventListener);
+            admin.getCache().removeCacheEventListener(cacheMapAccessEventListener);
+        }
+    }
+
+
+    /**
+     * Bug CACHE-241
+     */
+	public void testFlushDateTomorrow() {
+		GeneralCacheAdministrator cacheAdmin = new GeneralCacheAdministrator(null);
+		
+		cacheAdmin.putInCache("key1", "key1value");
+		
+		try {
+			assertNotNull(cacheAdmin.getFromCache("key1"));
+		} catch (NeedsRefreshException e1) {
+			fail("Previous cache key1 doesn't exsits in GCA for the test!");
+		}
+		
+		cacheAdmin.flushAll(new Date(System.currentTimeMillis() + 5000)); // flush in 5 sec.
+		try {
+			cacheAdmin.getFromCache("key1"); 
+		} catch (NeedsRefreshException e) {
+			cacheAdmin.cancelUpdate("key1");
+			fail("NRE is thrown, but key will expire in 5s."); // it fails here
+		}
+	}
+
+
+    /**
+     * Utility method that tries to get an item from the cache and verify
+     * if all goes as expected
+     * <p>
+     * @param key       The item key
+     * @param refresh   The timestamp specifiying if the item needs refresh
+     * @param exceptionExpected Specify if we expect a NeedsRefreshException
+     */
+    private Object checkObj(String key, int refresh, boolean exceptionExpected) {
+        // Cache content
+        Object content = null;
+
+        try {
+            // try to find an object
+            content = admin.getFromCache(key, refresh);
+
+            if (exceptionExpected) {
+                fail("Expected NeedsRefreshException!");
+            }
+        } catch (NeedsRefreshException nre) {
+            admin.cancelUpdate(key);
+
+            if (!exceptionExpected) {
+                fail("Did not expected NeedsRefreshException!");
+            }
+
+            // Return the cache content from the exception
+            content = nre.getCacheContent();
+        }
+
+        return content;
+    }
+
+    /**
+     * Method that try to retrieve data from the cache but specify wrong arguments
+     * <p>
+     * @param key         The cache item key
+     * @param refresh     The timestamp specifiying if the item needs refresh
+     */
+    private void invalidGetFromCacheArgument(String key, int refresh) {
+        try {
+            // Try to get the data from the cache
+            admin.getFromCache(key, refresh);
+            fail("getFromCache did NOT throw an IllegalArgumentException");
+        } catch (IllegalArgumentException ipe) {
+            // This is what we expect
+        } catch (NeedsRefreshException nre) {
+            admin.cancelUpdate(key);
+
+            // Ignore this one
+        }
+    }
+
+    /**
+     * Method that try to insert data in the cache but specify wrong arguments
+     * <p>
+     * @param key         The cache item key
+     * @param content     The content of the cache item
+     */
+    private void invalidPutInCacheArgument(String key, Object content) {
+        try {
+            // Try to put this data in the cache
+            admin.putInCache(key, content);
+            fail("putInCache did NOT throw an IllegalArgumentException");
+        } catch (IllegalArgumentException ipe) {
+            // This is what we expect
+        }
+    }
+
+    private void populate() {
+        for (int i = 0; i < 7; i++) {
+            String[] groups = ((i & 1) == 0) ? new String[] {GROUP1, GROUP2} : new String[] {
+                GROUP3
+            };
+            admin.putInCache(KEY + i, CONTENT + i, groups);
+        }
+
+        //register one miss.
+        checkObj("Not in cache", NO_REFRESH_NEEDED, true);
+
+        //register 7 hits
+        for (int i = 0; i < 7; i++) {
+            try {
+                admin.getFromCache(KEY + i, NO_REFRESH_NEEDED);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(KEY + i);
+            }
+        }
+
+        for (int i = 0; i < 7; i++) {
+            try {
+                admin.getFromCache(KEY + i, 0);
+            } catch (NeedsRefreshException e) {
+                admin.cancelUpdate(KEY + i);
+            }
+        }
+
+        admin.putInCache(KEY + 1, CONTENT);
+        admin.putInCache(KEY + 2, CONTENT);
+        admin.flushPattern("blahblah");
+        admin.flushGroup(GROUP1);
+        admin.flushGroup(GROUP2);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/BaseTestBroadcastingListener.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/BaseTestBroadcastingListener.java
new file mode 100644
index 0000000..0108641
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/BaseTestBroadcastingListener.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.*;
+
+import junit.framework.TestCase;
+
+import java.util.Date;
+
+/**
+ * A base class that provides the framework for testing a cluster listener
+ * implementation.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public abstract class BaseTestBroadcastingListener extends TestCase {
+    /**
+     * The persistance listener used for the tests
+     */
+    protected static AbstractBroadcastingListener listener = null;
+
+    /**
+     * A cache instance to use for the tests
+     */
+    protected static Cache cache = null;
+
+    /**
+     * The number of tests in this class. This is used to keep
+     * track of how many tests remain; once we reach zero we shut
+     * down the broadcasting listener.
+     */
+    int testsRemaining = 0;
+
+    /**
+     * Cache group
+     */
+    private final String GROUP = "test group";
+
+    /**
+     * Object key
+     */
+    private final String KEY = "Test clustersupport persistence listener key";
+
+    public BaseTestBroadcastingListener(String str) {
+        super(str);
+    }
+
+    /**
+     * Tests the listener by causing the cache to fire off all its
+     * events
+     */
+    public void testListener() {
+        CacheEntry entry = new CacheEntry(KEY, null);
+
+        cache.putInCache(KEY, entry);
+        cache.putInCache(KEY, entry, new String[] {GROUP});
+        cache.flushEntry(KEY);
+        cache.flushGroup(GROUP);
+        cache.flushAll(new Date());
+
+        // Note that the remove event is not called since it's not exposed.
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set up the broadcasting listener required for each test.
+     */
+    public void setUp() {
+        // At first invocation, create a listener
+        if (listener == null) {
+            testsRemaining = countTestCases(); // This seems to always return 1 even if there are multiple tests?
+
+            listener = getListener();
+            assertNotNull(listener);
+
+            cache = new Cache(true, false, false);
+            assertNotNull(cache);
+
+            try {
+                listener.initialize(cache, getConfig());
+            } catch (InitializationException e) {
+                fail(e.getMessage());
+            }
+
+            cache.addCacheEventListener(listener);
+        }
+    }
+
+    /**
+     * Once all the tests are complete this will shut down the broadcasting listener.
+     */
+    protected void tearDown() throws Exception {
+        if (--testsRemaining == 0) {
+            try {
+                listener.finialize();
+                listener = null;
+            } catch (FinalizationException e) {
+                fail(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Child classes implement this to return the broadcasting listener instance
+     * that will be tested.
+     */
+    abstract AbstractBroadcastingListener getListener();
+
+    /**
+     * Child classes implement this to return the configuration for their listener
+     * @return
+     */
+    abstract Config getConfig();
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/ListenForClusterTests.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/ListenForClusterTests.java
new file mode 100644
index 0000000..0e760f4
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/ListenForClusterTests.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.FinalizationException;
+import com.opensymphony.oscache.base.InitializationException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * <p>This should be used in conjunction with the cluster test cases. Run this
+ * program to set up listeners for the various clustering implementations so
+ * you can see that the test messages are being received correctly.</p>
+ *
+ * <p>A shutdown hook is installed so the listeners can be shut down cleanly.</p>
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class ListenForClusterTests {
+    ArrayList listeners = new ArrayList();
+    Cache cache;
+
+    private void mainLoop() {
+        Thread shutdownHook = new ShutdownHookThread("");
+        Runtime.getRuntime().addShutdownHook(shutdownHook);
+        System.out.println();
+        System.out.println("------------------------------------------------");
+        System.out.println("Waiting for cluster messages... (CTRL-C to exit)");
+        System.out.println("------------------------------------------------");
+
+        while (true) {
+            try {
+                Thread.sleep(250);
+            } catch (InterruptedException ie) {
+            }
+        }
+    }
+
+    private void initListeners() {
+        BaseTestBroadcastingListener testcase = null;
+        AbstractBroadcastingListener listener;
+        Cache cache = new Cache(true, false, false);
+
+        // Add the JavaGroups listener
+        try {
+            testcase = new TestJavaGroupsBroadcastingListener("JavaGroups");
+            listener = testcase.getListener();
+            listener.initialize(cache, testcase.getConfig());
+            cache.addCacheEventListener(listener);
+            listeners.add(listener);
+        } catch (InitializationException e) {
+            System.out.println("The JavaGroups listener could not be initialized: " + e);
+        }
+
+        // Add the JMS listener
+        try {
+            testcase = new TestJMSBroadcastingListener("JMS");
+            listener = testcase.getListener();
+
+            Config config = testcase.getConfig();
+            config.set("cache.cluster.jms.node.name", "cacheNode2");
+
+            listener.initialize(cache, config);
+            cache.addCacheEventListener(listener);
+            listeners.add(listener);
+        } catch (InitializationException e) {
+            System.out.println("The JMS listener could not be initialized: " + e);
+        }
+    }
+
+    /**
+     * Starts up the cluster listeners.
+     */
+    public static void main(String[] args) {
+        ListenForClusterTests listen = new ListenForClusterTests();
+
+        listen.initListeners();
+
+        listen.mainLoop();
+    }
+
+    /**
+     * Inner class that handles the shutdown event
+     */
+    class ShutdownHookThread extends Thread {
+        protected String message;
+
+        public ShutdownHookThread(String message) {
+            this.message = message;
+        }
+
+        /**
+         * This is executed when the application is forcibly shutdown (via
+         * CTRL-C etc). Any configured listeners are shut down here.
+         */
+        public void run() {
+            System.out.println("Shutting down the cluster listeners...");
+
+            for (Iterator it = listeners.iterator(); it.hasNext();) {
+                try {
+                    ((AbstractBroadcastingListener) it.next()).finialize();
+                } catch (FinalizationException e) {
+                    System.out.println("The listener could not be shut down cleanly: " + e);
+                }
+            }
+
+            System.out.println("Shutdown complete.");
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestCompleteClustering.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestCompleteClustering.java
new file mode 100644
index 0000000..7d00be3
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestCompleteClustering.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.plugins.clustersupport package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class TestCompleteClustering extends TestCase {
+    /**
+     * Constructor for the osCache project main test program
+     */
+    public TestCompleteClustering(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteClustering.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all OSCache clustering");
+        suite.addTest(TestJavaGroupsBroadcastingListener.suite());
+        suite.addTest(TestJMSBroadcastingListener.suite());
+        suite.addTest(TestJMS10BroadcastingListener.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMS10BroadcastingListener.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMS10BroadcastingListener.java
new file mode 100644
index 0000000..0f40f3a
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMS10BroadcastingListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Config;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test all the public methods of the broadcasting listener and assert the
+ * return values
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class TestJMS10BroadcastingListener extends BaseTestBroadcastingListener {
+    public TestJMS10BroadcastingListener(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit.
+     *
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestJMS10BroadcastingListener.class);
+    }
+
+    /**
+     * Returns a configured JavaGroupsBroadcastingListener instance
+     * for testing.
+     */
+    public AbstractBroadcastingListener getListener() {
+        return new JMS10BroadcastingListener();
+    }
+
+    /**
+     * Return the configuration for the JMS listener
+     */
+    Config getConfig() {
+        Config config = new Config();
+
+        // There needs to be an application resource file present "jndi.properties" that contains the following
+        // parameters:
+        //        config.set(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.ApplicationClientInitialContextFactory");
+        //        config.set(Context.PROVIDER_URL, "ormi://localhost:23791/");
+        //        config.set(Context.SECURITY_PRINCIPAL, "admin");
+        //        config.set(Context.SECURITY_CREDENTIALS, "xxxxxx");
+        config.set("cache.cluster.jms.topic.factory", "java:comp/env/jms/TopicConnectionFactory");
+        config.set("cache.cluster.jms.topic.name", "java:comp/env/jms/OSCacheTopic");
+        config.set("cache.cluster.jms.node.name", "cacheNode1");
+
+        return config;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMSBroadcastingListener.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMSBroadcastingListener.java
new file mode 100644
index 0000000..5a63b6c
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJMSBroadcastingListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Config;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test all the public methods of the broadcasting listener and assert the
+ * return values
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class TestJMSBroadcastingListener extends BaseTestBroadcastingListener {
+    public TestJMSBroadcastingListener(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit.
+     *
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestJMSBroadcastingListener.class);
+    }
+
+    /**
+     * Returns a configured JavaGroupsBroadcastingListener instance
+     * for testing.
+     */
+    public AbstractBroadcastingListener getListener() {
+        return new JMSBroadcastingListener();
+    }
+
+    /**
+     * Return the configuration for the JMS listener
+     */
+    Config getConfig() {
+        Config config = new Config();
+
+        // There needs to be an application resource file present "jndi.properties" that contains the following
+        // parameters:
+        //        config.set(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.ApplicationClientInitialContextFactory");
+        //        config.set(Context.PROVIDER_URL, "ormi://localhost:23791/");
+        //        config.set(Context.SECURITY_PRINCIPAL, "admin");
+        //        config.set(Context.SECURITY_CREDENTIALS, "xxxxxx");
+        config.set("cache.cluster.jms.topic.factory", "java:comp/env/jms/TopicConnectionFactory");
+        config.set("cache.cluster.jms.topic.name", "java:comp/env/jms/OSCacheTopic");
+        config.set("cache.cluster.jms.node.name", "cacheNode1");
+
+        return config;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJavaGroupsBroadcastingListener.java b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJavaGroupsBroadcastingListener.java
new file mode 100644
index 0000000..5de10bb
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/clustersupport/TestJavaGroupsBroadcastingListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.clustersupport;
+
+import com.opensymphony.oscache.base.Config;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test all the public methods of the broadcasting listener and assert the
+ * return values
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class TestJavaGroupsBroadcastingListener extends BaseTestBroadcastingListener {
+    public TestJavaGroupsBroadcastingListener(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit.
+     *
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestJavaGroupsBroadcastingListener.class);
+    }
+
+    /**
+     * Returns a configured JavaGroupsBroadcastingListener instance
+     * for testing.
+     */
+    public AbstractBroadcastingListener getListener() {
+        return new JavaGroupsBroadcastingListener();
+    }
+
+    /**
+     * Get the configuration for this listener
+     */
+    public Config getConfig() {
+        Config config = new Config();
+
+        // Just specify the IP and leave the rest of the settings at
+        // default values.
+        config.set("cache.cluster.multicast.ip", "231.12.21.132");
+
+        return config;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestCompleteDiskPersistence.java b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestCompleteDiskPersistence.java
new file mode 100644
index 0000000..efacf8d
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestCompleteDiskPersistence.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.plugins.diskpersistence package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteDiskPersistence.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author Lars Torunski
+ */
+public final class TestCompleteDiskPersistence extends TestCase {
+    /**
+     * Constructor for the osCache Cache Extra package main test program
+     */
+    public TestCompleteDiskPersistence(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteDiskPersistence.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the test suites of all the project classes
+        TestSuite suite = new TestSuite("Test all diskpersistence plugins");
+        suite.addTest(TestDiskPersistenceListener.suite());
+        suite.addTest(TestHashDiskPersistenceListener.suite());
+        //suite.addTest(TestUnSerializable.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestDiskPersistenceListener.java b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestDiskPersistenceListener.java
new file mode 100644
index 0000000..36a1cfa
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestDiskPersistenceListener.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.CachePersistenceException;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Test all the public methods of the disk persistance listener and assert the
+ * return values
+ *
+ * $Id: TestDiskPersistenceListener.java 422 2007-03-17 23:47:29Z larst $
+ * @version        $Revision: 422 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestDiskPersistenceListener extends TestCase {
+    /**
+     * Cache dir to persist to
+     */
+    public static final String CACHEDIR = "/tmp/diskcache";
+
+    /**
+     * The persistance listener used for the tests
+     */
+    private DiskPersistenceListener listener = null;
+
+    /**
+     * Object content
+     */
+    private final String CONTENT = "Disk persistance content";
+
+    /**
+     * Cache group
+     */
+    private final String GROUP = "test group";
+
+    /**
+     * Object key
+     */
+    private final String KEY = "Test disk persistance listener key";
+    private CacheFileFilter cacheFileFilter = new CacheFileFilter();
+
+    public TestDiskPersistenceListener(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestDiskPersistenceListener.class);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a listener
+        listener = new DiskPersistenceListener();
+
+        Properties p = new Properties();
+        p.setProperty("cache.path", CACHEDIR);
+        p.setProperty("cache.memory", "false");
+        p.setProperty("cache.persistence.class", "com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener");
+        listener.configure(new Config(p));
+    }
+
+    /**
+     * Test the cache directory removal
+     */
+    public void testClear() {
+        // Create an new element since we removed it at the last test
+        testStoreRetrieve();
+
+        // Remove the directory, and assert that we have no more entry
+        try {
+            listener.clear();
+            assertTrue(!listener.isStored(KEY));
+        } catch (CachePersistenceException cpe) {
+            cpe.printStackTrace();
+            fail("Exception thrown in test clear!");
+        }
+    }
+
+    /**
+     * Test that the previouly created file exists
+     */
+    public void testIsStored() {
+        try {
+            listener.store(KEY, CONTENT);
+
+            // Retrieve the previously created file
+            assertTrue(listener.isStored(KEY));
+
+            // Check that the fake key returns false
+            assertTrue(!listener.isStored(KEY + "fake"));
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("testIsStored raised an exception");
+        }
+    }
+
+    /**
+     * Test the cache removal
+     */
+    public void testRemove() {
+        // Create an entry if it doesn't exists
+        try {
+            if (!listener.isStored(KEY)) {
+                listener.store(KEY, CONTENT);
+            }
+
+            // Remove the previously created file
+            listener.remove(KEY);
+        } catch (CachePersistenceException cpe) {
+            cpe.printStackTrace();
+            fail("Exception thrown in test remove!");
+        }
+    }
+
+    /**
+     * Force CachePersistenceException to get a 100% in the unit test
+     */
+    public void testCachePersistenceException() {
+        try {
+            for (int i = 0; i < 2; i++) {
+                if (i == 1) throw new CachePersistenceException("test");             
+            }
+            fail("CachePersistenceException not thrown!");
+        } catch (CachePersistenceException cpe) {
+            // ignore
+        }
+        try {
+            for (int i = 0; i < 2; i++) {
+                if (i == 1) throw new CachePersistenceException();             
+            }
+            fail("CachePersistenceException not thrown!");
+        } catch (CachePersistenceException cpe) {
+            // ignore
+        }
+    }
+    
+    /**
+     * Test the disk store and retrieve
+     */
+    public void testStoreRetrieve() {
+        // Create a cache entry and store it
+        CacheEntry entry = new CacheEntry(KEY);
+        entry.setContent(CONTENT);
+
+        try {
+            listener.store(KEY, entry);
+
+            // Retrieve our entry and validate the values
+            CacheEntry newEntry = (CacheEntry) listener.retrieve(KEY);
+            assertTrue(entry.getContent().equals(newEntry.getContent()));
+            assertEquals(entry.getCreated(), newEntry.getCreated());
+            assertTrue(entry.getKey().equals(newEntry.getKey()));
+
+            // Try to retrieve a non-existent object
+            assertNull(listener.retrieve("doesn't exist"));
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!");
+        }
+    }
+
+    /**
+     * Test the storing and retrieving of groups
+     */
+    public void testStoreRetrieveGroups() {
+        // Store a group
+        Set groupSet = new HashSet();
+        groupSet.add("1");
+        groupSet.add("2");
+
+        try {
+            listener.storeGroup(GROUP, groupSet);
+
+            // Retrieve it and validate its contents
+            groupSet = listener.retrieveGroup(GROUP);
+            assertNotNull(groupSet);
+
+            assertTrue(groupSet.contains("1"));
+            assertTrue(groupSet.contains("2"));
+            assertFalse(groupSet.contains("3"));
+
+            // Try to retrieve a non-existent group
+            assertNull(listener.retrieveGroup("abc"));
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!");
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        listener.clear();
+        assertTrue("Cache not cleared", new File(CACHEDIR).list(cacheFileFilter).length == 0);
+    }
+
+    private static class CacheFileFilter implements FilenameFilter {
+        public boolean accept(File dir, String name) {
+            return !"__groups__".equals(name);
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestHashDiskPersistenceListener.java b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestHashDiskPersistenceListener.java
new file mode 100644
index 0000000..c5887de
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestHashDiskPersistenceListener.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2002-2007 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import com.opensymphony.oscache.base.CacheEntry;
+import com.opensymphony.oscache.base.Config;
+import com.opensymphony.oscache.base.persistence.CachePersistenceException;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Test all the public methods of the disk persistance listener and assert the
+ * return values
+ *
+ * $Id: TestHashDiskPersistenceListener.java 413 2007-03-13 23:03:50Z larst $
+ * @version        $Revision: 413 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestHashDiskPersistenceListener extends TestCase {
+    /**
+     * The persistance listener used for the tests
+     */
+    private HashDiskPersistenceListener listener = null;
+
+    /**
+     * Object content
+     */
+    private final String CONTENT = "Disk persistance content";
+
+    /**
+     * Cache group
+     */
+    private final String GROUP = "test group";
+
+    /**
+     * Object key
+     */
+    private final String KEY = "Test disk persistance listener key";
+    private CacheFileFilter cacheFileFilter = new CacheFileFilter();
+
+    public TestHashDiskPersistenceListener(String str) {
+        super(str);
+    }
+
+    /**
+     * This methods returns the name of this test class to JUnit
+     * <p>
+     * @return The test for this class
+     */
+    public static Test suite() {
+        return new TestSuite(TestHashDiskPersistenceListener.class);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // At first invocation, create a listener
+        listener = new HashDiskPersistenceListener();
+
+        Properties p = new Properties();
+        p.setProperty("cache.path", TestDiskPersistenceListener.CACHEDIR);
+        p.setProperty("cache.memory", "false");
+        p.setProperty("cache.persistence.class", "com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener");
+        p.setProperty("cache.persistence.disk.hash.algorithm", "MD5");
+        listener.configure(new Config(p));
+    }
+
+    /**
+     * Test the cache directory removal
+     */
+    public void testClear() {
+        // Create an new element since we removed it at the last test
+        testStoreRetrieve();
+
+        // Remove the directory, and assert that we have no more entry
+        try {
+            listener.clear();
+            assertTrue(!listener.isStored(KEY));
+        } catch (CachePersistenceException cpe) {
+            cpe.printStackTrace();
+            fail("Exception thrown in test clear!");
+        }
+    }
+
+    /**
+     * Test that the previouly created file exists
+     */
+    public void testIsStored() {
+        try {
+            listener.store(KEY, CONTENT);
+
+            // Retrieve the previously created file
+            assertTrue(listener.isStored(KEY));
+
+            // Check that the fake key returns false
+            assertTrue(!listener.isStored(KEY + "fake"));
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("testIsStored raised an exception");
+        }
+    }
+
+    /**
+     * Test the cache removal
+     */
+    public void testRemove() {
+        // Create an entry if it doesn't exists
+        try {
+            if (!listener.isStored(KEY)) {
+                listener.store(KEY, CONTENT);
+            }
+
+            // Remove the previously created file
+            listener.remove(KEY);
+        } catch (CachePersistenceException cpe) {
+            cpe.printStackTrace();
+            fail("Exception thrown in test remove!");
+        }
+    }
+
+    /**
+     * Test the disk store and retrieve
+     */
+    public void testStoreRetrieve() {
+        // Create a cache entry and store it
+        CacheEntry entry = new CacheEntry(KEY);
+        entry.setContent(CONTENT);
+
+        try {
+            listener.store(KEY, entry);
+
+            // Retrieve our entry and validate the values
+            CacheEntry newEntry = (CacheEntry) listener.retrieve(KEY);
+            assertTrue(entry.getContent().equals(newEntry.getContent()));
+            assertEquals(entry.getCreated(), newEntry.getCreated());
+            assertTrue(entry.getKey().equals(newEntry.getKey()));
+
+            // Try to retrieve a non-existent object
+            assertNull(listener.retrieve("doesn't exist"));
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!");
+        }
+    }
+
+    /**
+     * Test the storing and retrieving of groups
+     */
+    public void testStoreRetrieveGroups() {
+        // Store a group
+        Set groupSet = new HashSet();
+        groupSet.add("1");
+        groupSet.add("2");
+
+        try {
+            listener.storeGroup(GROUP, groupSet);
+
+            // Retrieve it and validate its contents
+            groupSet = listener.retrieveGroup(GROUP);
+            assertNotNull(groupSet);
+
+            assertTrue(groupSet.contains("1"));
+            assertTrue(groupSet.contains("2"));
+            assertFalse(groupSet.contains("3"));
+
+            // Try to retrieve a non-existent group
+            assertNull(listener.retrieveGroup("abc"));
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!");
+        }
+    }
+    
+    private static final byte[] BYTES_1 = {0x00};
+    private static final byte[] BYTES_2 = {0x00, 0x00};
+    private static final byte[] BYTES_3 = {0x00, 0x00, 0x00};
+    private static final byte[] BYTES_4 = {0x01};
+    
+    /**
+     * Test against bug issue CACHE-288.
+     */
+    public void testByteArrayToHexString() {
+        assertFalse("ByteArrayToHexStrings 1 and 2 shouldn't be equal", 
+                HashDiskPersistenceListener.byteArrayToHexString(BYTES_1).
+                equals(HashDiskPersistenceListener.byteArrayToHexString(BYTES_2)));
+        assertFalse("ByteArrayToHexStrings 1 and 3 shouldn't be equal", 
+                HashDiskPersistenceListener.byteArrayToHexString(BYTES_1).
+                equals(HashDiskPersistenceListener.byteArrayToHexString(BYTES_3)));
+        assertFalse("ByteArrayToHexStrings 1 and 4 shouldn't be equal", 
+                HashDiskPersistenceListener.byteArrayToHexString(BYTES_1).
+                equals(HashDiskPersistenceListener.byteArrayToHexString(BYTES_4)));
+        assertFalse("ByteArrayToHexStrings 1 and 4 shouldn't be equal", 
+                HashDiskPersistenceListener.byteArrayToHexString(BYTES_1).
+                equals(HashDiskPersistenceListener.byteArrayToHexString(BYTES_4)));
+    }
+
+    protected void tearDown() throws Exception {
+        listener.clear();
+        assertTrue("Cache not cleared", new File(TestDiskPersistenceListener.CACHEDIR).list(cacheFileFilter).length == 0);
+    }
+
+    private static class CacheFileFilter implements FilenameFilter {
+        public boolean accept(File dir, String name) {
+            return !"__groups__".equals(name);
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestUnSerializable.java b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestUnSerializable.java
new file mode 100644
index 0000000..a82844b
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/plugins/diskpersistence/TestUnSerializable.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created on Mar 11, 2005
+ *
+ * TODO To change the template for this generated file go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+package com.opensymphony.oscache.plugins.diskpersistence;
+
+import com.opensymphony.oscache.general.GeneralCacheAdministrator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.io.File;
+
+/**
+ * @author admin
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class TestUnSerializable extends TestCase {
+    final String CACHE_DIRECTORY_PATH = TestDiskPersistenceListener.CACHEDIR + "/application";
+    GeneralCacheAdministrator cache;
+
+    /* (non-Javadoc)
+     * @see junit.framework.TestCase#setUp()
+     */
+    protected void setUp() throws Exception {
+        // TODO Auto-generated method stub
+        super.setUp();
+
+        java.util.Properties properties = new java.util.Properties();
+        properties.setProperty("cache.path", TestDiskPersistenceListener.CACHEDIR);
+        properties.setProperty("cache.persistence.class", "com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener");
+        properties.setProperty("cache.persistence.overflow.only", "true");
+
+        //	        properties.setProperty("cache.memory", "false");
+        properties.setProperty("cache.capacity", "2");
+        properties.setProperty("cache.unlimited.disk", "false");
+        cache = new GeneralCacheAdministrator(properties);
+        cache.getCache().getPersistenceListener().clear();
+    }
+
+    /* (non-Javadoc)
+     * @see junit.framework.TestCase#tearDown()
+     */
+    protected void tearDown() throws Exception {
+        // TODO Auto-generated method stub
+        super.tearDown();
+    }
+
+    public void testNotSerializableObject() throws Exception {
+        cache.putInCache("1", new UnSerializable());
+        cache.putInCache("2", new UnSerializable());
+        assertTrue(isDirectoryEmpty(CACHE_DIRECTORY_PATH));
+        cache.putInCache("3", new UnSerializable());
+        cache.putInCache("4", new UnSerializable());
+        assertTrue(isDirectoryEmpty(CACHE_DIRECTORY_PATH));
+        cache.flushAll();
+    }
+
+    /**
+         * @param filePath
+         * @return
+         */
+    private boolean isDirectoryEmpty(String filePath) {
+        File file = new File(filePath);
+        return !file.exists() || (file.list().length == 0);
+    }
+
+    /**
+    * This methods returns the name of this test class to JUnit
+    * <p>
+    * @return The test for this class
+    */
+    public static Test suite() {
+        return new TestSuite(TestUnSerializable.class);
+    }
+
+    public static class UnSerializable {
+        int asdfasdfasdf = 234;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/util/TestFastCronParser.java b/src/test/java/com/opensymphony/oscache/util/TestFastCronParser.java
new file mode 100644
index 0000000..e3d6e90
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/util/TestFastCronParser.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.util;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.util.*;
+
+/**
+ *
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ * @author $Author: larst $
+ * @version $Revision: 385 $
+ */
+public class TestFastCronParser extends TestCase {
+    public TestFastCronParser(String str) {
+        super(str);
+    }
+
+    /**
+    * This methods returns the name of this test class to JUnit
+    * <p>
+    * @return The name of this class
+    */
+    public static Test suite() {
+        return new TestSuite(TestFastCronParser.class);
+    }
+
+    /**
+    * Tests to see if the cron class can calculate the previous matching
+    * time correctly in various circumstances
+    */
+    public void testEvaluations() {
+        // Minute tests
+        cronCall("01/01/2003 0:00", "45 * * * *", "31/12/2002 23:45", false);
+        cronCall("01/01/2003 0:00", "45-47,48,49 * * * *", "31/12/2002 23:49", false);
+        cronCall("01/01/2003 0:00", "2/5 * * * *", "31/12/2002 23:57", false);
+
+        // Hour tests
+        cronCall("20/12/2003 10:00", "* 3/4 * * *", "20/12/2003 07:59", false);
+        cronCall("20/12/2003 0:00", "* 3 * * *", "19/12/2003 03:59", false);
+
+        // Day of month tests
+        cronCall("07/01/2003 0:00", "30 * 1 * *", "01/01/2003 23:30", false);
+        cronCall("01/01/2003 0:00", "10 * 22 * *", "22/12/2002 23:10", false);
+        cronCall("01/01/2003 0:00", "30 23 19 * *", "19/12/2002 23:30", false);
+        cronCall("01/01/2003 0:00", "30 23 21 * *", "21/12/2002 23:30", false);
+        cronCall("01/01/2003 0:01", "* * 21 * *", "21/12/2002 23:59", false);
+        cronCall("10/07/2003 0:00", "* * 30,31 * *", "30/06/2003 23:59", false);
+
+        // Test month rollovers for months with 28,29,30 and 31 days
+        cronCall("01/03/2002 0:11", "* * * 2 *", "28/02/2002 23:59", false);
+        cronCall("01/03/2004 0:44", "* * * 2 *", "29/02/2004 23:59", false);
+        cronCall("01/04/2002 0:00", "* * * 3 *", "31/03/2002 23:59", false);
+        cronCall("01/05/2002 0:00", "* * * 4 *", "30/04/2002 23:59", false);
+
+        // Other month tests (including year rollover)
+        cronCall("01/01/2003 5:00", "10 * * 6 *", "30/06/2002 23:10", false);
+        cronCall("01/01/2003 5:00", "10 * * February,April-Jun *", "30/06/2002 23:10", false);
+        cronCall("01/01/2003 0:00", "0 12 1 6 *", "01/06/2002 12:00", false);
+        cronCall("11/09/1988 14:23", "* 12 1 6 *", "01/06/1988 12:59", false);
+        cronCall("11/03/1988 14:23", "* 12 1 6 *", "01/06/1987 12:59", false);
+        cronCall("11/03/1988 14:23", "* 2,4-8,15 * 6 *", "30/06/1987 15:59", false);
+        cronCall("11/03/1988 14:23", "20 * * january,FeB,Mar,april,May,JuNE,July,Augu,SEPT-October,Nov,DECEM *", "11/03/1988 14:20", false);
+
+        // Day of week tests
+        cronCall("26/06/2003 10:00", "30 6 * * 0", "22/06/2003 06:30", false);
+        cronCall("26/06/2003 10:00", "30 6 * * sunday", "22/06/2003 06:30", false);
+        cronCall("26/06/2003 10:00", "30 6 * * SUNDAY", "22/06/2003 06:30", false);
+        cronCall("23/06/2003 0:00", "1 12 * * 2", "17/06/2003 12:01", false);
+        cronCall("23/06/2003 0:00", "* * * * 3,0,4", "22/06/2003 23:59", false);
+        cronCall("23/06/2003 0:00", "* * * * 5", "20/06/2003 23:59", false);
+        cronCall("02/06/2003 18:30", "0 12 * * 2", "27/05/2003 12:00", false);
+        cronCall("02/06/2003 18:30", "0 12 * * Tue,Thurs-Sat,2", "31/05/2003 12:00", false);
+        cronCall("02/06/2003 18:30", "0 12 * * Mon-tuesday,wed,THURS-FRiday,Sat-SUNDAY", "02/06/2003 12:00", false);
+
+        // Leap year tests
+        cronCall("01/03/2003 12:00", "1 12 * * *", "28/02/2003 12:01", false); // non-leap year
+        cronCall("01/03/2004 12:00", "1 12 * * *", "29/02/2004 12:01", false); // leap year
+        cronCall("01/03/2003 12:00", "1 23 * * 0", "23/02/2003 23:01", false); // non-leap year
+        cronCall("01/03/2004 12:00", "1 23 * * 0", "29/02/2004 23:01", false); // leap year
+        cronCall("01/03/2003 12:00", "* * 29 2 *", "29/02/2000 23:59", false); // Find the previous leap-day
+        cronCall("01/02/2003 12:00", "* * 29 2 *", "29/02/2000 23:59", false); // Find the previous leap-day
+        cronCall("01/02/2004 12:00", "* * 29 2 *", "29/02/2000 23:59", false); // Find the previous leap-day
+
+        // Interval and range tests
+        cronCall("20/12/2003 10:00", "* */4 * * *", "20/12/2003 08:59", false);
+        cronCall("20/12/2003 10:00", "* 3/2 * * *", "20/12/2003 09:59", false);
+        cronCall("20/12/2003 10:00", "1-30/5 10-20/3 * jan-aug/2 *", "31/07/2003 19:26", false);
+        cronCall("20/12/2003 10:00", "20-25,27-30/2 10/8 * * *", "19/12/2003 18:29", false);
+    }
+
+    /**
+    * Tests a range of invalid cron expressions
+    */
+    public void testInvalidExpressionParsing() {
+        FastCronParser parser = new FastCronParser();
+
+        try {
+            parser.setCronExpression(null);
+            fail("An IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        } catch (ParseException e) {
+            fail("Expected an IllegalArgumentException but received a ParseException instead");
+        }
+
+        /**
+        * Not enough tokens
+        */
+        cronCall("01/01/2003 0:00", "", "", true);
+        cronCall("01/01/2003 0:00", "8 * 8/1 *", "", true);
+
+        /**
+        * Invalid syntax
+        */
+        cronCall("01/01/2003 0:00", "* invalid * * *", "", true);
+        cronCall("01/01/2003 0:00", "* -1 * * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 20 * 0", "", true);
+        cronCall("01/01/2003 0:00", "* * 5-6-7 * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 5/6-7 * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 5-* * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 5-6* * *", "", true);
+        cronCall("01/01/2003 0:00", "* * * * Mo", "", true);
+        cronCall("01/01/2003 0:00", "* * * jxxx *", "", true);
+        cronCall("01/01/2003 0:00", "* * * juxx *", "", true);
+        cronCall("01/01/2003 0:00", "* * * fbr *", "", true);
+        cronCall("01/01/2003 0:00", "* * * mch *", "", true);
+        cronCall("01/01/2003 0:00", "* * * mAh *", "", true);
+        cronCall("01/01/2003 0:00", "* * * arl *", "", true);
+        cronCall("01/01/2003 0:00", "* * * Spteber *", "", true);
+        cronCall("01/01/2003 0:00", "* * * otber *", "", true);
+        cronCall("01/01/2003 0:00", "* * * nvemtber *", "", true);
+        cronCall("01/01/2003 0:00", "* * * Dcmber *", "", true);
+        cronCall("01/01/2003 0:00", "* * * * mnday", "", true);
+        cronCall("01/01/2003 0:00", "* * * * tsdeday", "", true);
+        cronCall("01/01/2003 0:00", "* * * * wdnesday", "", true);
+        cronCall("01/01/2003 0:00", "* * * * frday", "", true);
+        cronCall("01/01/2003 0:00", "* * * * sdhdatr", "", true);
+
+        /**
+        * Values out of range
+        */
+        cronCall("01/01/2003 0:00", "* * 0 * *", "", true);
+        cronCall("01/01/2003 0:00", "* 50 * * *", "", true);
+        cronCall("01/01/2003 0:00", "* * * 1-20 *", "", true);
+        cronCall("01/01/2003 0:00", "* * 0-20 * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 1-40 * *", "", true);
+        cronCall("01/01/2003 0:00", "* * * 1 8", "", true);
+        cronCall("01/01/2003 0:00", "* * 0/3 * *", "", true);
+        cronCall("01/01/2003 0:00", "* * 30 2 *", "", true); // 30th Feb doesn't ever exist!
+        cronCall("01/01/2003 0:00", "* * 31 4 *", "", true); // 31st April doesn't ever exist!
+    }
+
+    /**
+    * This tests the performance of the cron parsing engine. Note that it may take
+    * a couple of minutes o run - by default this test is disabled. Comment out the
+    * <code>return</code> statement at the start of this method to enable the
+    * benchmarking.
+    */
+    public void testPerformance() {
+        if (true) {
+            //            return; // Comment out this line to benchmark
+        }
+
+        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
+        Date date = null;
+
+        try {
+            date = sdf.parse("21/01/2003 16:27");
+        } catch (ParseException e) {
+            fail("Failed to parse date. Please check your unit test code!");
+        }
+
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        calendar.setTime(date);
+
+        long baseTime = calendar.getTimeInMillis();
+
+        long time = 0;
+
+        try {
+            // Give HotSpot a chance to warm up
+            iterate("28 17 22 02 *", baseTime, time, 10000, true);
+
+            // Number of iterations to test
+            int count = 1000000;
+
+            // Test the best-case scenario
+            long bestCaseTime = iterate("* * * * *", baseTime, time, count, true);
+            System.out.println("Best case with parsing took " + bestCaseTime + "ms for " + count + " iterations. (" + (bestCaseTime / (float) count) + "ms per call)");
+
+            // Test a near worst-case scenario
+            long worstCaseTime = iterate("0-59,0-13,2,3,4,5 17-19 22-23,22,23 2,3 *", baseTime, time, count, true);
+            System.out.println("Worst case with parsing took " + worstCaseTime + "ms for " + count + " iterations. (" + (worstCaseTime / (float) count) + "ms per call)");
+
+            // Test the best-case scenario without parsing the expression on each iteration
+            bestCaseTime = iterate("* * * * *", baseTime, time, count, false);
+            System.out.println("Best case without parsing took " + bestCaseTime + "ms for " + count + " iterations. (" + (bestCaseTime / (float) count) + "ms per call)");
+
+            // Test a near worst-case scenario without parsing the expression on each iteration
+            worstCaseTime = iterate("0-59,0-13,2,3,4,5 17-19 22-23,22,23 2,3 *", baseTime, time, count, false);
+            System.out.println("Worst case without parsing took " + worstCaseTime + "ms for " + count + " iterations. (" + (worstCaseTime / (float) count) + "ms per call)");
+        } catch (ParseException e) {
+        }
+    }
+
+    /**
+    * Tests that a range of valid cron expressions get parsed correctly.
+    */
+    public void testValidExpressionParsing() {
+        FastCronParser parser;
+
+        // Check the default constructor
+        parser = new FastCronParser();
+        assertNull(parser.getCronExpression());
+
+        try {
+            parser = new FastCronParser("* * * * *");
+            assertEquals("* * * * *", parser.getCronExpression()); // Should be the same as what we gave it
+            assertEquals("* * * * *", parser.getExpressionSummary());
+
+            parser.setCronExpression("0  *  * *     *");
+            assertEquals("0  *  * *     *", parser.getCronExpression()); // Should be the same as what we gave it
+            assertEquals("0 * * * *", parser.getExpressionSummary());
+
+            parser.setCronExpression("5 10 * * 1,4,6");
+            assertEquals("5 10 * * 1,4,6", parser.getExpressionSummary());
+
+            parser.setCronExpression("0,5-20,4-15,24-27 0 *   2-4,5,6-3 *"); // Overlapping ranges, backwards ranges
+            assertEquals("0,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,24,25,26,27 0 * 2,3,4,5,6 *", parser.getExpressionSummary());
+        } catch (ParseException e) {
+            e.printStackTrace();
+            fail("Cron expression should have been valid: " + e);
+        }
+    }
+
+    /**
+    * Makes a call to the FastCronParser.
+    *
+    * @param dateStr   The date string to use as the base date. The format must be
+    * <code>"dd/MM/yyyy HH:mm"</code>.
+    * @param cronExpr  The cron expression to test.
+    * @param result    The expected result. This should be a date in the same format
+    * as <code>dateStr</code>.
+    * @param expectException Pass in <code>true</code> if the {@link FastCronParser} is
+    * expected to throw a <code>ParseException</code>.
+    */
+    private void cronCall(String dateStr, String cronExpr, String result, boolean expectException) {
+        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
+        Date date = null;
+
+        try {
+            date = sdf.parse(dateStr);
+        } catch (ParseException e) {
+            fail("Failed to parse date " + dateStr + ". Please check your unit test code!");
+        }
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+
+        long baseTime = calendar.getTimeInMillis();
+        FastCronParser parser = null;
+
+        try {
+            parser = new FastCronParser(cronExpr);
+
+            if (expectException) {
+                fail("Should have received a ParseException while parsing " + cronExpr);
+            }
+
+            long time = parser.getTimeBefore(baseTime);
+            assertEquals(result, sdf.format(new Date(time)));
+        } catch (ParseException e) {
+            if (!expectException) {
+                fail("Unexpected ParseException while parsing " + cronExpr + ": " + e);
+            }
+        }
+    }
+
+    /**
+    * Used by the benchmarking
+    */
+    private long iterate(String cronExpr, long baseTime, long time, int count, boolean withParse) throws ParseException {
+        long startTime = System.currentTimeMillis();
+
+        if (withParse) {
+            FastCronParser parser = new FastCronParser();
+
+            for (int i = 0; i < count; i++) {
+                parser.setCronExpression(cronExpr);
+                time = parser.getTimeBefore(baseTime);
+            }
+        } else {
+            FastCronParser parser = new FastCronParser(cronExpr);
+
+            for (int i = 0; i < count; i++) {
+                time = parser.getTimeBefore(baseTime);
+            }
+        }
+
+        long endTime = System.currentTimeMillis();
+        long duration = (endTime - startTime);
+        duration += (time - time); // Use the time variable to prevent it getting optimized away
+        return duration;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/CheckDeployment.java b/src/test/java/com/opensymphony/oscache/web/CheckDeployment.java
new file mode 100644
index 0000000..8da2dc4
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/CheckDeployment.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * User: hani
+ * Date: Jun 12, 2003
+ * Time: 3:34:20 PM
+ */
+public class CheckDeployment {
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            throw new IllegalArgumentException("No url specified to check");
+        }
+
+        try {
+            if (!args[0].endsWith("/")) {
+                args[0] = args[0] + "/";
+            }
+
+            URL url = new URL(args[0] + "oscache.txt");
+            URLConnection c = url.openConnection();
+            c.getInputStream();
+            System.exit(0);
+        } catch (java.net.MalformedURLException e) {
+            System.out.println("Invalid url for oscache webapp:" + args[0]);
+        } catch (ConnectException ex) {
+            System.out.println("Error connecting to server at '" + args[0] + "', ensure that the webserver for the oscache example application is running");
+        } catch (FileNotFoundException e) {
+            System.out.println("Error connecting to webapp at '" + args[0] + "', ensure that the example-war app is deployed correctly at the specified url");
+        } catch (IOException e) {
+            System.out.println("Error connecting to webapp at '" + args[0] + "', ensure that the example-war app is deployed correctly at the specified url");
+        }
+
+        System.exit(1);
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/TestCompleteWeb.java b/src/test/java/com/opensymphony/oscache/web/TestCompleteWeb.java
new file mode 100644
index 0000000..d3f7926
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/TestCompleteWeb.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.web package.
+ * It invokes all the test suites of all the other classes of the package.
+ *
+ * $Id: TestCompleteWeb.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestCompleteWeb extends TestCase {
+    /**
+     * Constructor for the osCache project main test program
+     */
+    public TestCompleteWeb(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestCompleteWeb.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        // Add all the tests suite of all the project classes
+        TestSuite suite = new TestSuite("Test all osCache web");
+        suite.addTest(TestOscacheJsp.suite());
+        suite.addTest(TestOscacheServlet.suite());
+        suite.addTest(TestOscacheFilter.suite());
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/TestLoadCompleteWeb.java b/src/test/java/com/opensymphony/oscache/web/TestLoadCompleteWeb.java
new file mode 100644
index 0000000..90a8081
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/TestLoadCompleteWeb.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.clarkware.junitperf.LoadTest;
+import com.clarkware.junitperf.RandomTimer;
+
+import junit.extensions.RepeatedTest;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for the com.opensymphony.oscache.web package.
+ * It invokes all the test suites of all the other classes of the package.
+ * The test methods will be invoked with many users and iterations to simulate
+ * load on request
+ *
+ * $Id: TestLoadCompleteWeb.java 254 2005-06-17 05:07:38Z dres $
+ * @version        $Revision: 254 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestLoadCompleteWeb extends TestCase {
+    /**
+     * Constructor for the osCache Cache project main test program
+     */
+    public TestLoadCompleteWeb(String str) {
+        super(str);
+    }
+
+    /**
+     * Main method which is called to perform the tests
+     * <p>
+     * @param   args    Arguments received
+     */
+    public static void main(String[] args) {
+        // Run the test suite
+        junit.swingui.TestRunner testRunner = new junit.swingui.TestRunner();
+        testRunner.setLoading(false);
+
+        String[] args2 = {TestLoadCompleteWeb.class.getName()};
+        testRunner.start(args2);
+    }
+
+    /**
+     * Test suite required to test this project
+     * <p>
+     * @return  suite   The test suite
+     */
+    public static Test suite() {
+        final int clientThreads = 10; // Simulate 10 client threads
+        final int iterations = 20; // Simulate each user doing 20 iterations
+
+        TestSuite suite = new TestSuite("Test all osCache web");
+
+        // Ramp up a thread each 500 ms (+-100ms) until total number of threads reached
+        RandomTimer tm = new RandomTimer(300, 100);
+
+        // JSP
+        Test repeatedTest = new RepeatedTest(new TestOscacheJsp("testOscacheBasicForLoad"), iterations);
+        Test loadTest = new LoadTest(repeatedTest, clientThreads, tm);
+        suite.addTest(loadTest);
+
+        // Servlet
+        repeatedTest = new RepeatedTest(new TestOscacheServlet("testOscacheServletBasicForLoad"), iterations);
+        loadTest = new LoadTest(repeatedTest, clientThreads, tm);
+        suite.addTest(loadTest);
+
+        // Filter
+        repeatedTest = new RepeatedTest(new TestOscacheFilter("testOscacheFilterBasicForLoad"), iterations);
+        loadTest = new LoadTest(repeatedTest, clientThreads, tm);
+        suite.addTest(loadTest);
+
+        return suite;
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/TestOscacheFilter.java b/src/test/java/com/opensymphony/oscache/web/TestOscacheFilter.java
new file mode 100644
index 0000000..b79121b
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/TestOscacheFilter.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.meterware.httpunit.WebConversation;
+import com.meterware.httpunit.WebResponse;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the caching filter distributed with the package.
+ *
+ * $Id: TestOscacheFilter.java 435 2007-04-01 10:47:02Z larst $
+ * @version        $Revision: 435 $
+ * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
+ */
+public final class TestOscacheFilter extends TestCase {
+    // The instance of a webconversation to invoke pages
+    WebConversation wc = null;
+    private final String BASE_PAGE = "filter/filterTest.jsp";
+
+    // Constants definition
+    private final String BASE_URL_SYSTEM_PRP = "test.web.baseURL";
+    private final String PARAM_1 = "abc=123";
+    private final String PARAM_2 = "xyz=321";
+    private final String SESSION_ID = "jsessionid=12345678";
+    // Constants definition to access OscacheServlet
+    private final String SERVLET_URL = "cacheServlet/?";
+    private final String FORCE_REFRESH = "forceRefresh=true&";
+
+
+    /**
+     * Constructor required by JUnit
+     * <p>
+     * @param str Test name
+     */
+    public TestOscacheFilter(String str) {
+        super(str);
+    }
+
+    /**
+     * Returns the test suite for the test class
+     * <p>
+     * @return   Test suite for the class
+     */
+    public static Test suite() {
+        return new TestSuite(TestOscacheFilter.class);
+    }
+
+    /**
+     * Setup method called before each testXXXX of the class
+     */
+    public void setUp() {
+        // Create a web conversation to invoke our filter
+        if (wc == null) {
+            wc = new WebConversation();
+        }
+        compileJSP(constructURL(BASE_PAGE));
+    }
+
+    /**
+     * Test the OSCache filter
+     */
+    public void testOscacheFilter() {
+        String baseUrl = constructURL(BASE_PAGE);
+
+        // Flush the cache to avoid getting refreshed content from previous tests
+        flushCache();
+
+        // Call the page for the second time
+        String stringResponse = invokeURL(baseUrl, 200);
+
+        // Connect again, we should have the same content
+        String newResponse = invokeURL(baseUrl, 0);
+        assertTrue("new response " + newResponse + " should be the same to " + stringResponse, stringResponse.equals(newResponse));
+
+        // Try again with a session ID this time. The session ID should get filtered
+        // out of the cache key so the content should be the same
+        newResponse = invokeURL(baseUrl + "?" + SESSION_ID, 200);
+        assertTrue("new response by a session id request " + newResponse + " should be the same to " + stringResponse, stringResponse.equals(newResponse));
+
+        // Connect again with extra params, the content should be different
+        newResponse = invokeURL(baseUrl + "?" + PARAM_1 + "&" + PARAM_2, 500);
+        assertFalse("new response " + newResponse + " expected it to be different to last one.", stringResponse.equals(newResponse));
+
+        stringResponse = newResponse;
+
+        // Connect again with the parameters in a different order. We should still
+        // get the same content.
+        newResponse = invokeURL(baseUrl + "?" + PARAM_2 + "&" + PARAM_1, 0);
+        assertTrue("order of parameters shouldn't change the response", stringResponse.equals(newResponse));
+
+        // Connect again with the same parameters, but throw the session ID into
+        // the mix again. The content should remain the same.
+        newResponse = invokeURL(baseUrl + "?" + SESSION_ID + "&" + PARAM_1 + "&" + PARAM_2, 0);
+        assertTrue("a session id shouldn't change the response either", stringResponse.equals(newResponse));
+    }
+
+    /**
+     * Test the OSCache filter with fast requests
+     */
+    public void testOSCacheFilterFast() {
+        String baseUrl = constructURL(BASE_PAGE);
+
+        for (int i = 0; i < 10; i++) {
+            // Flush the cache to avoid getting refreshed content from previous tests
+            flushCache();
+            // build the url
+            String url = baseUrl + "?i=" + i;
+            String response = invokeURL(url, 100);
+            for (int j = 0; j < 5; j++) {
+                String newResponse = invokeURL(url, 100);
+                assertTrue("Fast: new response (i="+i+",j="+j+") " + newResponse + " should be the same to " + response, response.equals(newResponse));
+            }
+        }
+    }
+
+    /**
+     * Test the cache module using a filter and basic load
+     */
+    public void testOscacheFilterBasicForLoad() {
+        String baseUrl = constructURL(BASE_PAGE);
+
+        for (int i = 0; i < 5; i++) {
+            String stringResponse = invokeURL(baseUrl, 0);
+
+            // Check we received something slightly sane
+            assertTrue(stringResponse.indexOf("Current Time") > 0);
+        }
+    }
+
+    /**
+     * Compile a JSP page by invoking it. We compile the page first to avoid
+     * the compilation delay when testing since the time is a crucial factor
+     *
+     * @param URL The JSP url to invoke
+     */
+    private void compileJSP(String URL) {
+        try {
+            // Invoke the URL
+            wc.getResponse(URL);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!!");
+        }
+    }
+
+    /** 
+     * Flushes the cache to avoid recieving content from previous tests
+     */
+    private void flushCache() {
+        String flushUrl = constructURL(SERVLET_URL + FORCE_REFRESH);
+        
+        String stringResponse = invokeURL(flushUrl, 0);
+        
+        assertTrue("Flushing the cache failed!", stringResponse.indexOf("This is some cache content") > 0);
+        
+        // avoid that flush time is equal to last update time of a new entry
+        try {
+            Thread.sleep(5);
+        } catch (InterruptedException ignore) {
+        }
+    }
+
+    /**
+     *  Reads the base url from the test.web.baseURL system property and
+     *  append the given URL.
+     *  <p>
+     *  @param Url  Url to append to the base.
+     *  @return Complete URL
+     */
+    private String constructURL(String url) {
+        String base = System.getProperty(BASE_URL_SYSTEM_PRP);
+        String constructedUrl = null;
+
+        if (base != null) {
+            if (!base.endsWith("/")) {
+                base = base + "/";
+            }
+
+            constructedUrl = base + url;
+        } else {
+            fail("System property test.web.baseURL needs to be set to the proper server to use.");
+        }
+
+        return constructedUrl;
+    }
+
+    /**
+     * Utility method to request a URL and then sleep some time before returning
+     * <p>
+     * @param url         The URL of the page to invoke
+     * @param sleepTime   The time to sleep before returning
+     * @return The text value of the reponse (HTML code)
+     */
+    private String invokeURL(String url, int sleepTime) {
+        try {
+            // Invoke the JSP and wait the specified sleepTime
+            WebResponse resp = wc.getResponse(url);
+            Thread.sleep(sleepTime);
+
+            return resp.getText();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!!");
+
+            return null;
+        }
+    }
+    
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/TestOscacheJsp.java b/src/test/java/com/opensymphony/oscache/web/TestOscacheJsp.java
new file mode 100644
index 0000000..f0b4a8b
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/TestOscacheJsp.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.meterware.httpunit.WebConversation;
+import com.meterware.httpunit.WebResponse;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test test the JSPs distributed with the package. It checks that the
+ * cache integration is OK.
+ *
+ * $Id: TestOscacheJsp.java 385 2006-10-07 06:57:10Z larst $
+ * @version        $Revision: 385 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestOscacheJsp extends TestCase {
+    // The instance of a webconversation to invoke pages
+    WebConversation wc = null;
+    private final String APPLICATION_SCOPE = "scope=application&";
+
+    // Constants definition
+    private final String BASE_URL_SYSTEM_PRP = "test.web.baseURL";
+    private final String FIRST_PAGE = "oscacheTest.jsp?";
+    private final String FORCE_CACHE_USE = "forcecacheuse=yes&";
+    private final String FORCE_REFRESH = "refresh=true";
+    //private final String PAGE_SCOPE = "scope=page&";
+    //private final String REQUEST_SCOPE = "scope=request&";
+    private final String SECOND_PAGE = "oscacheTestMultipleTagNoKey.jsp?";
+    private final String SESSION_SCOPE = "scope=session&";
+    private final int CACHE_TAG_EXPIRATION = 2000;
+    private final int HALF_CACHE_TAG_EXPIRATION = CACHE_TAG_EXPIRATION / 2;
+
+    /**
+     * Constructor required by JUnit
+     * <p>
+     * @param str Test name
+     */
+    public TestOscacheJsp(String str) {
+        super(str);
+    }
+
+    /**
+     * Returns the test suite for the test class
+     * <p>
+     * @return   Test suite for the class
+     */
+    public static Test suite() {
+        return new TestSuite(TestOscacheJsp.class);
+    }
+
+    /**
+     * Setup method called before each testXXXX of the class
+     */
+    public void setUp() {
+        // Create a web conversation to invoke our JSP
+        if (wc == null) {
+            wc = new WebConversation();
+        }
+    }
+
+    /**
+     * Test the cache module under load
+     */
+    public void testOscacheBasicForLoad() {
+        String baseUrl = constructURL(FIRST_PAGE);
+
+        // Connect to the JSP using the application scope
+        String stringResponse = invokeJSP(baseUrl, CACHE_TAG_EXPIRATION);
+
+        // Assert that a page was properly generated.
+        // This does not ensure that the cache is working properly.
+        // Though, it ensures that no exception or other weird problem occured
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+
+        // Invoke the JSP page containing 2 cache tag
+        baseUrl = constructURL(SECOND_PAGE);
+
+        // Connect to the JSP using the application scope
+        stringResponse = invokeJSP(baseUrl, CACHE_TAG_EXPIRATION);
+
+        // Assert that a page was properly generated.
+        // This does not ensure that the cache is working properly.
+        // Though, it ensures that no exception or other weird problem occured
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+    }
+
+    /**
+     * Test the cache module using a JSP
+     */
+    public void testOscacheJsp() {
+        String baseUrl = constructURL(FIRST_PAGE);
+
+        // Connect to a session scope to allow the JSP compilation
+        compileJSP(baseUrl + SESSION_SCOPE);
+
+        // Connect to the JSP using the application scope
+        String stringResponse = invokeJSP(baseUrl, HALF_CACHE_TAG_EXPIRATION);
+
+        // Connect again, we should have the same content since it expires
+        // only each 2 seconds
+        assertTrue(stringResponse.equals(invokeJSP(baseUrl, HALF_CACHE_TAG_EXPIRATION)));
+
+        // Connect again, the content should be different
+        String newResponse = invokeJSP(baseUrl, CACHE_TAG_EXPIRATION + (CACHE_TAG_EXPIRATION / 4));
+        assertTrue(!stringResponse.equals(newResponse));
+        stringResponse = newResponse;
+
+        // Connect again, but request the cache content so no refresh should occur
+        assertTrue(stringResponse.equals(invokeJSP(baseUrl, FORCE_CACHE_USE, 0)));
+
+        // Connect again, the content should have changed
+        newResponse = invokeJSP(baseUrl, HALF_CACHE_TAG_EXPIRATION);
+        assertTrue(!stringResponse.equals(newResponse));
+        stringResponse = newResponse;
+
+        // Connect for the last time, force the cache
+        // refresh so the content should have changed
+        assertTrue(!stringResponse.equals(invokeJSP(baseUrl, FORCE_REFRESH, 0)));
+
+        // Invoke the JSP page containing 2 cache tag
+        baseUrl = constructURL(SECOND_PAGE);
+        compileJSP(baseUrl + SESSION_SCOPE);
+        stringResponse = invokeJSP(baseUrl, CACHE_TAG_EXPIRATION);
+
+        // Invoke the same page en check if it's identical
+        assertTrue(stringResponse.equals(invokeJSP(baseUrl, CACHE_TAG_EXPIRATION)));
+    }
+
+    /**
+     * Compile a JSP page by invoking it. We compile the page first to avoid
+     * the compilation delay when testing since the time is a crucial factor
+     * <p>
+     * @param URL The JSP url to invoke
+     */
+    private void compileJSP(String URL) {
+        try {
+            // Invoke the JSP
+            wc.getResponse(URL);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!!");
+        }
+    }
+
+    /**
+     *  Reads the base url from the test.web.baseURL system property and
+     *  append the given URL.
+     *  <p>
+     *  @param Url  Url to append to the base.
+     *  @return Complete URL
+     */
+    private String constructURL(String Url) {
+        String base = System.getProperty(BASE_URL_SYSTEM_PRP);
+        String constructedUrl = null;
+
+        if (base != null) {
+            if (!base.endsWith("/")) {
+                base = base + "/";
+            }
+
+            constructedUrl = base + Url;
+        } else {
+            fail("System property test.web.baseURL needs to be set to the proper server to use.");
+        }
+
+        return constructedUrl;
+    }
+
+    /**
+     * Utility method to invoke a JSP page and then sleep some time before returning
+     * <p>
+     * @param baseUrl     The URL of the JSP to invoke
+     * @param sleepTime   THe time to sleep before returning
+     * @return The text value of the reponse (HTML code)
+     */
+    private String invokeJSP(String baseUrl, int sleepTime) {
+        return invokeJSP(baseUrl, "", sleepTime);
+    }
+
+    /**
+     * Utility method to invoke a JSP page and then sleep some time before returning
+     * <p>
+     * @param baseUrl     The URL of the JSP to invoke
+     * @param URLparam    The URL parameters of the JSP to invoke
+     * @param sleepTime   The time to sleep before returning
+     * @return The text value of the reponse (HTML code)
+     */
+    private String invokeJSP(String baseUrl, String URLparam, int sleepTime) {
+        try {
+            // Invoke the JSP and wait the specified sleepTime
+            WebResponse resp = wc.getResponse(baseUrl + APPLICATION_SCOPE + URLparam);
+            Thread.sleep(sleepTime);
+
+            return resp.getText();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised!!");
+
+            return null;
+        }
+    }
+}
diff --git a/src/test/java/com/opensymphony/oscache/web/TestOscacheServlet.java b/src/test/java/com/opensymphony/oscache/web/TestOscacheServlet.java
new file mode 100644
index 0000000..e7cf5f3
--- /dev/null
+++ b/src/test/java/com/opensymphony/oscache/web/TestOscacheServlet.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.meterware.httpunit.WebConversation;
+import com.meterware.httpunit.WebResponse;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test test the osCacheServlet distributed with the package. It checks that the
+ * cache integration is OK.
+ *
+ * $Id: TestOscacheServlet.java 314 2005-10-16 18:30:25Z ltorunski $
+ * @version        $Revision: 314 $
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public final class TestOscacheServlet extends TestCase {
+
+    // The instance of a webconversation to invoke pages
+    static WebConversation wc = null;
+    private final String APPLICATION_SCOPE = "scope=application&";
+
+    // Constants definition
+    private final String BASE_URL_SYSTEM_PRP = "test.web.baseURL";
+    private final String FORCE_CACHE_USE = "forcecacheuse=yes&";
+    private final String FORCE_REFRESH = "forceRefresh=true&";
+    private final String KEY = "key=ServletKeyItem&";
+    private final String REFRESH_PERIOD = "refreshPeriod=";
+    private final String SERVLET_URL = "/cacheServlet/?";
+    private final int NO_REFRESH_WANTED = 2000;
+    private final int REFRESH_WANTED = 0;
+
+    /**
+     * Constructor required by JUnit
+     * <p>
+     * @param str Test name
+     */
+    public TestOscacheServlet(String str) {
+        super(str);
+    }
+
+    /**
+     * Returns the test suite for the test class
+     * <p>
+     * @return   Test suite for the class
+     */
+    public static Test suite() {
+        return new TestSuite(TestOscacheServlet.class);
+    }
+
+    /**
+     * This method is invoked before each testXXXX methods of the
+     * class. It set ups the variables required for each tests.
+     */
+    public void setUp() {
+        // Create a web conversation on first run
+        if (wc == null) {
+            wc = new WebConversation();
+        }
+    }
+    
+    /**
+     * Test the cache module using a servlet
+     */
+    public void testOscacheServlet() {
+        // Make a first call just to initialize the servlet
+        String newResponse = invokeServlet(NO_REFRESH_WANTED);
+
+        // Connect to the servlet using the application scope
+        String previousReponse = invokeServlet(NO_REFRESH_WANTED);
+
+        // Call again an verify that the content hasn't changed
+        newResponse = invokeServlet(NO_REFRESH_WANTED);
+        assertTrue("new response " + newResponse + " should be the same to " + previousReponse, previousReponse.equals(newResponse));
+
+        // Call again an verify that the content is updated
+        newResponse = invokeServlet(REFRESH_WANTED);
+        assertFalse("new response " + newResponse + " expected it to be different to last one.", previousReponse.equals(newResponse));
+        previousReponse = newResponse;
+
+        // Call short delay so content should be refresh, but it will not since
+        // we ask to use the item already in cache
+        newResponse = invokeServlet(REFRESH_WANTED, FORCE_CACHE_USE);
+        assertTrue("new response " + newResponse + " should be the same to " + previousReponse, previousReponse.equals(newResponse));
+
+        // Call with long delay so the item would not need refresh, but we'll ask
+        // a refresh anyway
+        newResponse = invokeServlet(NO_REFRESH_WANTED, FORCE_REFRESH);
+        assertFalse("new response " + newResponse + " expected it to be different to last one.", previousReponse.equals(newResponse));
+
+        // Verify that the cache key and the cache entry are present in the output and
+        // that their values are correct
+        assertTrue("response '" + previousReponse + "' does not contain oscache string", previousReponse.indexOf("oscache") != -1);
+
+        assertTrue("response '" + previousReponse + "' does not contain /Test_key string", previousReponse.indexOf("/Test_key") != -1);
+    }
+
+    /**
+     * Test the cache module using a servlet and basic load
+     */
+    public void testOscacheServletBasicForLoad() {
+        // Call Servlet
+        String stringResponse = invokeServlet(NO_REFRESH_WANTED);
+
+        // Assert that a page was properly generated.
+        // This does not ensure that the cache is working properly.
+        // Though, it ensures that no exception or other weird problem occured
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+
+        // Call again
+        stringResponse = invokeServlet(REFRESH_WANTED);
+
+        // idem comment
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+
+        // Call again
+        stringResponse = invokeServlet(REFRESH_WANTED, FORCE_CACHE_USE);
+
+        // idem comment
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+
+        // Call again
+        stringResponse = invokeServlet(NO_REFRESH_WANTED, FORCE_REFRESH);
+
+        // idem comment
+        assertTrue(stringResponse.indexOf("This is some cache content") > 0);
+    }
+
+    /**
+     *  Reads the base url from the test.web.baseURL system property and
+     *  append the given URL.
+     *  <p>
+     *  @param Url  Url to append to the base.
+     *  @return Complete URL
+     */
+    private String constructURL(String Url) {
+        String base = System.getProperty(BASE_URL_SYSTEM_PRP);
+        String constructedUrl = null;
+
+        if (base != null) {
+            if (base.endsWith("/")) {
+                base = base.substring(0, base.length() - 1);
+            }
+
+            constructedUrl = base + Url;
+        } else {
+            fail("System property test.web.baseURL needs to be set to the proper server to use.");
+        }
+
+        return constructedUrl;
+    }
+
+    /**
+     * Utility method to invoke a servlet
+     * <p>
+     * @param refresh The time interval telling if the item needs refresh
+     * @return The HTML page returned by the servlet
+     */
+    private String invokeServlet(int refresh) {
+        // Invoke the servlet
+        return invokeServlet(refresh, "");
+    }
+
+    /**
+     * Utility method to invoke a servlet
+     * <p>
+     * @param refresh The time interval telling if the item needs refresh
+     * @param URL The URL of the servlet
+     * @return The HTML page returned by the servlet
+     */
+    private String invokeServlet(int refresh, String URL) {
+        // wait 10 millis to change the time, see System.currentTimeMillis() in OscacheServlet
+        try {
+            Thread.sleep(10);
+        } catch (InterruptedException ignore) {
+        }
+
+        // Invoke the servlet
+        try {
+            String request = constructURL(SERVLET_URL) + APPLICATION_SCOPE + KEY + REFRESH_PERIOD + refresh + "&" + URL;
+            WebResponse resp = wc.getResponse(request);
+            return resp.getText();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            fail("Exception raised! " + ex.getMessage());
+            return "";
+        }
+    }
+}
diff --git a/src/test/java/oscacheDiskAndMemory.properties b/src/test/java/oscacheDiskAndMemory.properties
new file mode 100644
index 0000000..4facba1
--- /dev/null
+++ b/src/test/java/oscacheDiskAndMemory.properties
@@ -0,0 +1,11 @@
+# CACHE IN MEMORY
+cache.memory=true
+
+# CACHE SIZE
+cache.capacity=100
+
+# CACHE PERSISTENCE CLASS
+cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+
+# CACHE DIRECTORY
+cache.path=/tmp/cachetagscache
diff --git a/src/test/java/oscacheDiskOnly.properties b/src/test/java/oscacheDiskOnly.properties
new file mode 100644
index 0000000..4f0e071
--- /dev/null
+++ b/src/test/java/oscacheDiskOnly.properties
@@ -0,0 +1,8 @@
+# CACHE IN MEMORY
+cache.memory=false
+
+# CACHE PERSISTENCE CLASS
+cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+
+# CACHE DIRECTORY
+cache.path=/tmp/cachetagscache
diff --git a/src/test/java/oscacheDiskOnlyHash.properties b/src/test/java/oscacheDiskOnlyHash.properties
new file mode 100644
index 0000000..c7c7f6a
--- /dev/null
+++ b/src/test/java/oscacheDiskOnlyHash.properties
@@ -0,0 +1,8 @@
+# CACHE IN MEMORY
+cache.memory=false
+
+# CACHE PERSISTENCE CLASS
+cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener
+
+# CACHE DIRECTORY
+cache.path=/tmp/cachetagscache
diff --git a/src/test/java/oscacheMemoryAndOverflowToDisk.properties b/src/test/java/oscacheMemoryAndOverflowToDisk.properties
new file mode 100644
index 0000000..6d7946e
--- /dev/null
+++ b/src/test/java/oscacheMemoryAndOverflowToDisk.properties
@@ -0,0 +1,11 @@
+# CACHE IN MEMORY
+cache.memory=true
+
+# CACHE PERSISTENCE CLASS
+cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+
+# CACHE DIRECTORY
+cache.path=/tmp/cachetagscache
+
+# CACHE OVERFLOW
+cache.persistence.overflow.only=true
diff --git a/src/test/java/oscacheMemoryOnly.properties b/src/test/java/oscacheMemoryOnly.properties
new file mode 100644
index 0000000..26f54a5
--- /dev/null
+++ b/src/test/java/oscacheMemoryOnly.properties
@@ -0,0 +1,8 @@
+# CACHE IN MEMORY
+cache.memory=true
+
+# CACHE LISTENERS
+cache.event.listeners=com.opensymphony.oscache.extra.StatisticListenerImpl
+
+# CACHE SIZE
+cache.capacity=1000
\ No newline at end of file
diff --git a/src/webapp/WEB-INF/classes/com/opensymphony/oscache/web/OscacheServlet.java b/src/webapp/WEB-INF/classes/com/opensymphony/oscache/web/OscacheServlet.java
new file mode 100644
index 0000000..7dca2ce
--- /dev/null
+++ b/src/webapp/WEB-INF/classes/com/opensymphony/oscache/web/OscacheServlet.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.oscache.web;
+
+import com.opensymphony.oscache.base.NeedsRefreshException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * Servlet used to test the web portion of osCache. It performs the operations
+ * received by parameter
+ *
+ * $Id: OscacheServlet.java 42 2003-07-17 20:28:07Z chris_miller $
+ * @version        $Revision: 42 $
+ * @author <a href="mailto:fbeauregard at pyxis-tech.com">Francois Beauregard</a>
+ * @author <a href="mailto:abergevin at pyxis-tech.com">Alain Bergevin</a>
+ */
+public class OscacheServlet extends HttpServlet {
+    /** Output content type */
+    private static final String CONTENT_TYPE = "text/html";
+
+    /** Clean up resources */
+    public void destroy() {
+    }
+
+    /**
+     * Process the HTTP Get request
+     * <p>
+     * @param request The HTTP request
+     * @param response The servlet response
+     * @throws ServletException
+     * @throws IOException
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        boolean varForceRefresh = false;
+        int refreshPeriod = 0;
+        int scope = PageContext.APPLICATION_SCOPE;
+        String forceCacheUse = null;
+        String key = null;
+
+        // Cache item
+        Long item;
+
+        // Get the admin
+        ServletCacheAdministrator admin = ServletCacheAdministrator.getInstance(getServletContext());
+
+        // Translate parameters
+        try {
+            String paramValue = request.getParameter("forceRefresh");
+
+            if ((paramValue != null) && (paramValue.length() > 0)) {
+                varForceRefresh = Boolean.valueOf(paramValue).booleanValue();
+            }
+
+            paramValue = request.getParameter("scope");
+
+            if ((paramValue != null) && (paramValue.length() > 0)) {
+                scope = getScope(paramValue);
+            }
+
+            paramValue = request.getParameter("refreshPeriod");
+
+            if ((paramValue != null) && (paramValue.length() > 0)) {
+                refreshPeriod = Integer.valueOf(paramValue).intValue();
+            }
+
+            forceCacheUse = request.getParameter("forcecacheuse");
+            key = request.getParameter("key");
+        } catch (Exception e) {
+            getServletContext().log("Error while retrieving the servlet parameters: " + e.toString());
+        }
+
+        // Check if all the items should be flushed
+        if (varForceRefresh) {
+            admin.flushAll();
+        }
+
+        try {
+            // Get the data from the cache
+            item = (Long) admin.getFromCache(scope, request, key, refreshPeriod);
+        } catch (NeedsRefreshException nre) {
+            // Check if we want to force the use of an item already in cache
+            if ("yes".equals(forceCacheUse)) {
+                admin.cancelUpdate(scope, request, key);
+                item = (Long) nre.getCacheContent();
+            } else {
+                item = new Long(System.currentTimeMillis());
+                admin.putInCache(scope, request, key, item);
+            }
+        }
+
+        // Generate the output
+        response.setContentType(CONTENT_TYPE);
+
+        PrintWriter out = response.getWriter();
+        out.println("<html>");
+        out.println("<head><title>OscacheServlet</title></head>");
+        out.println("<body>");
+        out.println("<b>This is some cache content </b>: " + item.toString() + "<br>");
+        out.println("<b>Cache key</b>: " + admin.getCacheKey() + "<br>");
+        out.println("<b>Entry key</b>: " + admin.generateEntryKey("Test_key", request, scope) + "<br>");
+        out.println("</body></html>");
+    }
+
+    /**Initialize global variables*/
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+    }
+
+    /**
+     * Return the scope number corresponding to it's string name
+     */
+    private int getScope(String value) {
+        if ((value != null) && (value.equalsIgnoreCase("session"))) {
+            return PageContext.SESSION_SCOPE;
+        } else {
+            return PageContext.APPLICATION_SCOPE;
+        }
+    }
+}
diff --git a/src/webapp/WEB-INF/classes/oscache-cachefilter-disableCacheOnMethods.properties b/src/webapp/WEB-INF/classes/oscache-cachefilter-disableCacheOnMethods.properties
new file mode 100644
index 0000000..4452100
--- /dev/null
+++ b/src/webapp/WEB-INF/classes/oscache-cachefilter-disableCacheOnMethods.properties
@@ -0,0 +1,8 @@
+# CACHE KEY
+cache.key=__oscache_cachefilter_disableCacheOnMethods
+
+# CACHE LISTENERS
+cache.event.listeners=com.opensymphony.oscache.extra.ScopeEventListenerImpl
+
+# CACHE SIZE
+cache.capacity=10
diff --git a/src/webapp/WEB-INF/classes/oscache.properties b/src/webapp/WEB-INF/classes/oscache.properties
new file mode 100644
index 0000000..5f3bf9b
--- /dev/null
+++ b/src/webapp/WEB-INF/classes/oscache.properties
@@ -0,0 +1,140 @@
+# CACHE IN MEMORY
+#
+# If you want to disable memory caching, just uncomment this line.
+#
+# 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
+#
+# These hook OSCache events and perform various actions such as logging
+# cache hits and misses, or broadcasting to other cache instances across a cluster.
+# See the documentation for further information.
+#
+# cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroadcastingListener,  \
+#                       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. If you use the supplied DiskPersistenceListener,
+# don't forget to supply the cache.path property to specify the location of the cache
+# directory.
+# 
+# If a persistence class is not specified, OSCache will use memory caching only.
+#
+# cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
+
+# CACHE OVERFLOW PERSISTENCE
+# Use persistent cache in overflow or not. The default value is false, which means
+# the persistent cache will be used at all times for every entry.  true is the recommended setting.
+#
+# cache.persistence.overflow.only=true
+
+
+# CACHE DIRECTORY
+#
+# This is the directory on disk where caches will be stored by the DiskPersistenceListener.
+# it will be created if it doesn't already exist. Remember that OSCache must have
+# write permission to this directory.
+#
+# Note: for Windows machines, this needs \ to be escaped
+# ie Windows:
+# cache.path=c:\\myapp\\cache
+# or *ix:
+# cache.path=/opt/myapp/cache
+#
+# cache.path=c:\\app\\cache
+
+
+# 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.LRUCache
+# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
+# cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
+
+# THREAD BLOCKING BEHAVIOR
+#
+# When a request is made for a stale cache entry, it is possible that another thread is already
+# in the process of rebuilding that entry. This setting specifies how OSCache handles the
+# subsequent 'non-building' threads. The default behaviour (cache.blocking=false) is to serve
+# the old content to subsequent threads until the cache entry has been updated. This provides
+# the best performance (at the cost of serving slightly stale data). When blocking is enabled,
+# threads will instead block until the new cache entry is ready to be served. Once the new entry
+# is put in the cache the blocked threads will be restarted and given the new entry.
+# Note that even if blocking is disabled, when there is no stale data available to be served
+# threads will block until the data is added to the cache by the thread that is responsible
+# for building the data.
+#
+# cache.blocking=false
+
+
+# CACHE SIZE
+#
+# Default cache size in number of items. 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. The default value is false, which means
+# the disk cache will be limited in size to the value specified by cache.capacity.
+#
+# cache.unlimited.disk=false
+
+
+# JMS CLUSTER PROPERTIES
+#
+# Configuration properties for JMS clustering. See the clustering documentation
+# for more information on these settings.
+#
+#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
+#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
+#cache.cluster.jms.node.name=node1
+
+
+# JAVAGROUPS 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 clustering documentation and 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.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
+#UNICAST(timeout=300,600,1200,2400):\
+#pbcast.STABLE(desired_avg_gossip=20000):\
+#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
diff --git a/src/webapp/WEB-INF/web.xml b/src/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..b3a47ef
--- /dev/null
+++ b/src/webapp/WEB-INF/web.xml
@@ -0,0 +1,60 @@
+<!DOCTYPE web-app
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app>
+
+    <display-name>OSCache</display-name>
+
+    <filter>
+        <filter-name>CacheFilter</filter-name>
+        <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
+    </filter>
+
+    <filter>
+        <filter-name>CacheFilter-disableCacheOnMethods</filter-name>
+        <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
+        <init-param>
+            <param-name>time</param-name>
+            <param-value>60</param-value>
+        </init-param>
+        <init-param>
+            <param-name>disableCacheOnMethods</param-name>
+            <param-value>POST,PUT,DELETE</param-value>
+        </init-param>
+        <init-param>
+            <param-name>oscache-properties-file</param-name>
+            <param-value>/oscache-cachefilter-disableCacheOnMethods.properties</param-value>
+        </init-param>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>CacheFilter</filter-name>
+        <url-pattern>/filter/*</url-pattern>
+    </filter-mapping>
+    
+    <filter-mapping>
+        <filter-name>CacheFilter-disableCacheOnMethods</filter-name>
+        <url-pattern>/filter2/*</url-pattern>
+    </filter-mapping>
+    
+    <listener>
+        <listener-class>com.opensymphony.oscache.web.CacheContextListener</listener-class>
+    </listener>
+
+    <servlet>
+        <servlet-name>OSCacheServlet</servlet-name>
+        <servlet-class>com.opensymphony.oscache.web.OscacheServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>OSCacheServlet</servlet-name>
+        <url-pattern>/cacheServlet/*</url-pattern>
+    </servlet-mapping>
+
+    <session-config>
+        <session-timeout>10</session-timeout>
+    </session-config>
+
+</web-app>
diff --git a/src/webapp/cachetest.jsp b/src/webapp/cachetest.jsp
new file mode 100644
index 0000000..fae276d
--- /dev/null
+++ b/src/webapp/cachetest.jsp
@@ -0,0 +1,65 @@
+<%@ page import="java.util.*" %>
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<%
+String scope = "application";
+if (request.getParameter("scope") != null)
+{
+	scope = request.getParameter("scope");
+}
+%>
+<head>
+<title>Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+
+<form action="cachetest.jsp">
+	<input type="checkbox" name="refresh" value="now"> Refresh <br>
+	<b>Scope</b> : <select name="scope">
+		<option value="application" <%= scope.equals("application") ? "selected" : "" %>>Application
+		<option value="session" <%= scope.equals("session") ? "selected" : "" %>>Session
+		<option value="request" <%= scope.equals("request") ? "selected" : "" %>>Request
+		<option value="page" <%= scope.equals("page") ? "selected" : "" %>>Page
+	</select> <br>
+	<input type="submit" value="Refresh">
+</form>
+
+<% Date start = new Date(); %> <b>Start Time</b>: <%= start %><p>
+<hr>
+ <%-- Note that we have to supply a cache key otherwise the 'refresh' parameter
+         causes the refreshed page to end up with a different cache key! --%>
+<cache:cache key="test"
+	refresh='<%= request.getParameter("refresh") == null ? false : true %>'
+	scope="<%= scope %>">
+	<b>Cache Time</b>: <%= new Date() %><br>
+	<% try { %>
+	Inside try block. <br>
+	<%
+	Thread.sleep(1000L); // Kill some time
+	if ((new Date()).getTime() % 5 == 0)
+	{
+		System.out.println("THROWING EXCEPTION....");
+		throw new Exception("ack!");
+	}
+	%>
+	<p>
+
+	<% }
+	catch (Exception e)
+	{
+	%>
+		Using cached content: <cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+<b>End Time</b>: <%= new Date() %><p>
+
+<b>Running Time</b>: <%= (new Date()).getTime() - start.getTime() %> ms.<p>
+</body>
\ No newline at end of file
diff --git a/src/webapp/cronTest.jsp b/src/webapp/cronTest.jsp
new file mode 100644
index 0000000..10d724d
--- /dev/null
+++ b/src/webapp/cronTest.jsp
@@ -0,0 +1,172 @@
+<%@ page import="java.util.*" %>
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<%
+String scope = "application";
+if (request.getParameter("scope") != null)
+{
+	scope = request.getParameter("scope");
+}
+
+boolean refresh = false;
+if (request.getParameter("refresh") != null)
+{
+	refresh = true;
+}
+
+boolean forceCacheUse = false;
+if (request.getParameter("forceCacheUse") != null)
+{
+	forceCacheUse = true ;
+}
+%>
+<head>
+<title>Cron Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<em>The cached content for the current day of the week should expire every minute.
+Try setting your system clock to a couple of minutes before midnight and watch what
+happens when you refresh the page as the day rolls over.</em>
+<hr>
+<b>Time this page was last refreshed: </b>: <%= new Date() %><br>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest1"
+	cron="* * * * Sunday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Sunday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest2"
+	cron="* * * * Monday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Monday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest3"
+	cron="* * * * Tuesday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Tuesday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest4"
+	cron="* * * * Wednesday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Wednesday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest5"
+	cron="* * * * Thursday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Thursday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest6"
+	cron="* * * * Friday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Friday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="cronTest7"
+	cron="* * * * Saturday">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (expires according to the cron expression "* * * * Saturday")</b><br><br>
+	<%
+	if (forceCacheUse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+<hr>
+</body>
+</html>
diff --git a/src/webapp/filter/filterTest.jsp b/src/webapp/filter/filterTest.jsp
new file mode 100644
index 0000000..7b0ef83
--- /dev/null
+++ b/src/webapp/filter/filterTest.jsp
@@ -0,0 +1,14 @@
+<%@ page import="java.util.*" %>
+<head>
+<title>Filter Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<hr>
+<b>Current Time</b>: <%= new Date() %><br>
+<b>Current Time Millis</b>: <%= System.currentTimeMillis() %><br>
+</body>
+</html>
diff --git a/src/webapp/filter2/filterTestDisableCacheOnMethods.jsp b/src/webapp/filter2/filterTestDisableCacheOnMethods.jsp
new file mode 100644
index 0000000..ec57e6c
--- /dev/null
+++ b/src/webapp/filter2/filterTestDisableCacheOnMethods.jsp
@@ -0,0 +1,23 @@
+<%@ page import="java.util.*" %>
+<head>
+<title>Filter Test Disable Cache On Methods Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<hr>
+<b>This is the POST vs. GET cache content example (refresh time = 60 seconds).</b><br>
+<br>
+<b>Time this page was last refreshed: </b>: <%= new Date() %><br><br>
+<b>Current Time Millis</b>: <%= System.currentTimeMillis() %><br>
+<br>
+<a href="filterTestDisableCacheOnMethods.jsp">GET with the cache</a><p>
+<br>
+<br>
+<form action="filterTestDisableCacheOnMethods.jsp" method="post">
+	<input type="submit" value="POST without the cache">
+</form>
+</body>
+</html>
diff --git a/src/webapp/groupTest.jsp b/src/webapp/groupTest.jsp
new file mode 100644
index 0000000..8b54b9e
--- /dev/null
+++ b/src/webapp/groupTest.jsp
@@ -0,0 +1,42 @@
+<%@ page import="java.util.*" %>
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<head>
+<title>Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<hr>Flushing 'group2'...<hr>
+<cache:flush group='group2' scope='application'/>
+<hr>
+<cache:cache key='test1' groups='group1,group2' duration='5s'>
+    <b>Cache Time</b>: <%= (new Date()).getTime() %><br>
+    This is some cache content test1 that is in 'group1' and 'group2'. Normally it would refresh if it
+    was more than 5 seconds old, however the <cache:flush group='group2' scope='application'>
+    tag causes this entry to be flushed on every page refresh.<br>
+</cache:cache>
+<hr>
+<cache:cache key='test2' groups='group1' duration='5s'>
+    <b>Cache Time</b>: <%= (new Date()).getTime() %><br>
+    This is some cache content test2 that is in 'group1' (refreshes if more than 5 seconds old)<br>
+</cache:cache>
+<hr>
+<cache:cache key='test3' duration='20s'>
+    <b>Cache Time</b>: <%= (new Date()).getTime() %><br>
+    This is some cache content test3 that is in 'group1' and 'group2'. The groups are added using the <cache:addgroup /> tag.<br>
+    <cache:addgroup group='group1'/>
+    <cache:addgroup group='group2'/>
+</cache:cache>
+<hr>
+<cache:cache key='test4' duration='20s'>
+    <b>Cache Time</b>: <%= (new Date()).getTime() %><br>
+    This is some cache content test4 that is in 'group1' and 'group2'. The groups are added using the <cache:addgroups /> tag.<br>
+    <cache:addgroups groups='group1,group2'/>
+</cache:cache>
+<hr>
+</body>
+</html>
diff --git a/src/webapp/index.html b/src/webapp/index.html
new file mode 100644
index 0000000..0c31ec0
--- /dev/null
+++ b/src/webapp/index.html
@@ -0,0 +1,20 @@
+<head>
+<title>Index Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+Please choose one of the following:
+<ul>
+<li><a href="cachetest.jsp">cachetest.jsp</a></li>
+<li><a href="oscacheTest.jsp">oscacheTest.jsp</a></li>
+<li><a href="groupTest.jsp">groupTest.jsp</a></li>
+<li><a href="cronTest.jsp">cronTest.jsp</a></li>
+<li><a href="silentTest.jsp">silentTest.jsp</a></li>
+<li><a href="filter/filterTest.jsp">filter/filterTest.jsp</a></li>
+<li><a href="filter2/filterTestDisableCacheOnMethods.jsp">filter2/filterTestDisableCacheOnMethods.jsp</a></li>
+<li><a href="oscacheTestMultipleTagNoKey.jsp">oscacheTestMultipleTagNoKey.jsp</a></li>
+<li><a href="cacheServlet?refreshPeriod=10">OSCache Test Servlet</a></li>
+</ul>
+</body>
\ No newline at end of file
diff --git a/src/webapp/oscache.txt b/src/webapp/oscache.txt
new file mode 100644
index 0000000..48cdce8
--- /dev/null
+++ b/src/webapp/oscache.txt
@@ -0,0 +1 @@
+placeholder
diff --git a/src/webapp/oscacheTest.jsp b/src/webapp/oscacheTest.jsp
new file mode 100644
index 0000000..4791232
--- /dev/null
+++ b/src/webapp/oscacheTest.jsp
@@ -0,0 +1,52 @@
+<%@ page import="java.util.*" %>
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<%
+String scope = "application";
+if (request.getParameter("scope") != null)
+{
+	scope = request.getParameter("scope");
+}
+
+boolean refresh = false;
+if (request.getParameter("refresh") != null)
+{
+	refresh = true;
+}
+
+boolean forcecacheuse = false;
+if (request.getParameter("forcecacheuse") != null)
+{
+	forcecacheuse = true ;
+}
+%>
+<head>
+<title>Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<hr>
+ <cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	key="oscacheTest"
+	duration="2s">
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b>This is some cache content (refresh = 2 seconds)</b><br><br>
+	<%
+	if (forcecacheuse)
+	{
+	%>
+		<cache:usecached />
+	<%
+	}
+	%>
+</cache:cache>
+</body>
+</html>
diff --git a/src/webapp/oscacheTestMultipleTagNoKey.jsp b/src/webapp/oscacheTestMultipleTagNoKey.jsp
new file mode 100644
index 0000000..9fec67a
--- /dev/null
+++ b/src/webapp/oscacheTestMultipleTagNoKey.jsp
@@ -0,0 +1,265 @@
+<%@ page import="java.util.*" %>
+
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<%
+
+String scope = "application";
+
+if (request.getParameter("scope") != null)
+
+{
+
+	scope = request.getParameter("scope");
+
+}
+
+
+
+boolean refresh = false;
+if (request.getParameter("refresh") != null)
+{
+	refresh = true;
+}
+
+boolean forcecacheuse = false;
+if (request.getParameter("forcecacheuse") != null)
+{
+	forcecacheuse = true ;
+}
+%>
+<head>
+<title>Test Page</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+
+<hr>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='150'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh = 150</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	duration='PT5M'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=300</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	duration='PT5M1S'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=301</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='302'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=302</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='303'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=303</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	duration='PT304S'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=304</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='305'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=305</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	duration='PT5M6S'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=306</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='307'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=307</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='308'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=308</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='309'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=309</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='310'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=310</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='311'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=311</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='312'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=312</b><br><br>
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='313'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=313</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='314'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=314</b><br><br>
+</cache:cache>
+<cache:cache
+ 	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='315'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=315</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='316'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=316</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='317'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=317</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='318'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=318</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='319'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=319</b><br><br>
+
+</cache:cache>
+<cache:cache
+	refresh='<%= refresh %>'
+	scope="<%= scope %>"
+	time='320'>
+
+	<b>Cache Time</b>: <%= new Date() %><br>
+
+	<b> This is some cache content refresh=320</b><br><br>
+
+</cache:cache>
+<hr>
+</body>
+</html>
+
+
+
diff --git a/src/webapp/silentTest.jsp b/src/webapp/silentTest.jsp
new file mode 100644
index 0000000..70d588f
--- /dev/null
+++ b/src/webapp/silentTest.jsp
@@ -0,0 +1,26 @@
+<%@ page import="java.util.*" %>
+<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache" %>
+
+<head>
+<title>Test Page - Silent Mode</title>
+<style type="text/css">
+body {font-family: Arial, Verdana, Geneva, Helvetica, sans-serif}
+</style>
+</head>
+<body>
+
+<a href="<%= request.getContextPath() %>/">Back to index</a><p>
+<hr>
+
+Testing the silent attribute...<br/>
+<cache:cache key="silentTest" mode="silent" duration="10s">
+This content was inserted into the cache silently. Duration = 10 seconds. Current time = <%= new Date() %>
+</cache:cache>
+<br/>Some content has been silently cached<br/>
+<hr>
+<cache:cache key="silentTest">
+You should never get to see this text!
+</cache:cache>
+
+</body>
+</html>
diff --git a/www/index.html b/www/index.html
new file mode 100644
index 0000000..8a1835e
--- /dev/null
+++ b/www/index.html
@@ -0,0 +1,6 @@
+<html>
+  <body>
+    A caching solution that includes a JSP tag library and set of classes to perform fine grained dynamic caching of JSP content, servlet responses or arbitrary objects. It provides both in memory and persistent on disk caches, and can allow your site to have graceful error tolerance (eg if an error occurs like your db goes down, you can serve the cached content so people can still surf the site almost without knowing).
+    <p>The OSCache Homepage is at <a href="http://www.opensymphony.com/oscache">www.opensymphony.com/oscache</a></p>
+  </body>
+</html>
diff --git a/www/project_tools.html b/www/project_tools.html
new file mode 100644
index 0000000..aeb62e1
--- /dev/null
+++ b/www/project_tools.html
@@ -0,0 +1,4 @@
+<div><a href="http://www.opensymphony.com">OpenSymphony</a></div>
+<div><a href="http://www.opensymphony.com/oscache/wiki/Documentation.html">Documentation</a></div>
+<div><a href="http://jira.opensymphony.com/browse/CACHE">Bug Tracker</a></div>
+<div><a href="http://wiki.opensymphony.com/display/CACHE">Wiki</a></div>
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/oscache.git



More information about the pkg-java-commits mailing list